Overview

Namespaces

  • None
  • sinacloud
    • sae

Classes

  • SaeADPNS
  • SaeAPNS
  • SaeChannel
  • SaeDeferredJob
  • SaeFetchInternal
  • SaeKV
  • SaeMail
  • SaeMysql
  • SaeSegment
  • SaeTAdvance
  • SaeTaskQueue
  • SaeTClientV2
  • SaeTOAuthV2
  • SaeVCode
  • sinacloud\sae\Storage
  • vDisk

Exceptions

  • OAuthException
  • sinacloud\sae\StorageException
  • Overview
  • Namespace
  • Class
   1:    2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16:   17:   18:   19:   20:   21:   22:   23:   24:   25:   26:   27:   28:   29:   30:   31:   32:   33:   34:   35:   36:   37:   38:   39:   40:   41:   42:   43:   44:   45:   46:   47:   48:   49:   50:   51:   52:   53:   54:   55:   56:   57:   58:   59:   60:   61:   62:   63:   64:   65:   66:   67:   68:   69:   70:   71:   72:   73:   74:   75:   76:   77:   78:   79:   80:   81:   82:   83:   84:   85:   86:   87:   88:   89:   90:   91:   92:   93:   94:   95:   96:   97:   98:   99:  100:  101:  102:  103:  104:  105:  106:  107:  108:  109:  110:  111:  112:  113:  114:  115:  116:  117:  118:  119:  120:  121:  122:  123:  124:  125:  126:  127:  128:  129:  130:  131:  132:  133:  134:  135:  136:  137:  138:  139:  140:  141:  142:  143:  144:  145:  146:  147:  148:  149:  150:  151:  152:  153:  154:  155:  156:  157:  158:  159:  160:  161:  162:  163:  164:  165:  166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176:  177:  178:  179:  180:  181:  182:  183:  184:  185:  186:  187:  188:  189:  190:  191:  192:  193:  194:  195:  196:  197:  198:  199:  200:  201:  202:  203:  204:  205:  206:  207:  208:  209:  210:  211:  212:  213:  214:  215:  216:  217:  218:  219:  220:  221:  222:  223:  224:  225:  226:  227:  228:  229:  230:  231:  232:  233:  234:  235:  236:  237:  238:  239:  240:  241:  242:  243:  244:  245:  246:  247:  248:  249:  250:  251:  252:  253:  254:  255:  256:  257:  258:  259:  260:  261:  262:  263:  264:  265:  266:  267:  268:  269:  270:  271:  272:  273:  274:  275:  276:  277:  278:  279:  280:  281:  282:  283:  284:  285:  286:  287:  288:  289:  290:  291:  292:  293:  294:  295:  296:  297:  298:  299:  300:  301:  302:  303:  304:  305:  306:  307:  308:  309:  310:  311:  312:  313:  314:  315:  316:  317:  318:  319:  320:  321:  322:  323:  324:  325:  326:  327:  328:  329:  330:  331:  332:  333:  334:  335:  336:  337:  338:  339:  340:  341:  342:  343:  344:  345:  346:  347:  348:  349:  350:  351:  352:  353:  354:  355:  356:  357:  358:  359:  360:  361:  362:  363:  364:  365:  366:  367:  368:  369:  370:  371:  372:  373:  374:  375:  376:  377:  378:  379:  380:  381:  382:  383:  384:  385:  386:  387:  388:  389:  390:  391:  392:  393:  394:  395:  396:  397:  398:  399:  400:  401:  402:  403:  404:  405:  406:  407:  408:  409:  410:  411:  412:  413:  414:  415:  416:  417:  418:  419:  420:  421:  422:  423:  424:  425:  426:  427:  428:  429:  430:  431:  432:  433:  434:  435:  436:  437:  438:  439:  440:  441:  442:  443:  444:  445:  446:  447:  448:  449:  450:  451:  452:  453:  454:  455:  456:  457:  458:  459:  460:  461:  462:  463:  464:  465:  466:  467:  468:  469:  470:  471:  472:  473:  474:  475:  476:  477:  478:  479:  480:  481:  482:  483:  484:  485:  486:  487:  488:  489:  490:  491:  492:  493:  494:  495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505:  506:  507:  508:  509:  510:  511:  512:  513:  514:  515:  516:  517:  518:  519:  520:  521:  522:  523:  524:  525:  526:  527:  528:  529:  530:  531:  532:  533:  534:  535:  536:  537:  538:  539:  540:  541:  542:  543:  544:  545:  546:  547:  548:  549:  550:  551:  552:  553:  554:  555:  556:  557:  558:  559:  560:  561:  562:  563:  564:  565:  566:  567:  568:  569:  570:  571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582:  583:  584:  585:  586:  587:  588:  589:  590:  591:  592:  593:  594:  595:  596:  597:  598:  599:  600:  601:  602:  603:  604:  605:  606:  607:  608:  609:  610:  611:  612:  613:  614:  615:  616:  617:  618:  619:  620:  621:  622:  623:  624:  625:  626:  627:  628:  629:  630:  631:  632:  633:  634:  635:  636:  637:  638:  639:  640:  641:  642:  643:  644:  645:  646:  647:  648:  649:  650:  651:  652:  653:  654:  655:  656:  657:  658:  659:  660:  661:  662:  663:  664:  665:  666:  667:  668:  669:  670:  671:  672:  673:  674:  675:  676:  677:  678:  679:  680:  681:  682:  683:  684:  685:  686:  687:  688:  689:  690:  691:  692:  693:  694:  695:  696:  697:  698:  699:  700:  701:  702:  703:  704:  705:  706:  707:  708:  709:  710:  711:  712:  713:  714:  715:  716:  717:  718:  719:  720:  721:  722:  723:  724:  725:  726:  727:  728:  729:  730:  731:  732:  733:  734:  735:  736:  737:  738:  739:  740:  741:  742:  743:  744:  745:  746:  747:  748:  749:  750:  751:  752:  753:  754:  755:  756:  757:  758:  759:  760:  761:  762:  763:  764:  765:  766:  767:  768:  769:  770:  771:  772:  773:  774:  775:  776:  777:  778:  779:  780:  781:  782:  783:  784:  785:  786:  787:  788:  789:  790:  791:  792:  793:  794:  795:  796:  797:  798:  799:  800:  801:  802:  803:  804:  805:  806:  807:  808:  809:  810:  811:  812:  813:  814:  815:  816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827:  828:  829:  830:  831:  832:  833:  834:  835:  836:  837:  838:  839:  840:  841:  842:  843:  844:  845:  846:  847:  848:  849:  850:  851:  852:  853:  854:  855:  856:  857:  858:  859:  860:  861:  862:  863:  864:  865:  866:  867:  868:  869:  870:  871:  872:  873:  874:  875:  876:  877:  878:  879:  880:  881:  882:  883:  884:  885:  886:  887:  888:  889:  890:  891:  892:  893:  894:  895:  896:  897:  898:  899:  900:  901:  902:  903:  904:  905:  906:  907:  908:  909:  910:  911:  912:  913:  914:  915:  916:  917:  918:  919:  920:  921:  922:  923:  924:  925:  926:  927:  928:  929:  930:  931:  932:  933:  934:  935:  936:  937:  938:  939:  940:  941:  942:  943:  944:  945:  946:  947:  948:  949:  950:  951:  952:  953:  954:  955:  956:  957:  958:  959:  960:  961:  962:  963:  964:  965:  966:  967:  968:  969:  970:  971:  972:  973:  974:  975:  976:  977:  978:  979:  980:  981:  982:  983:  984:  985:  986:  987:  988:  989:  990:  991:  992:  993:  994:  995:  996:  997:  998:  999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 1064: 1065: 1066: 1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 1085: 1086: 1087: 1088: 1089: 1090: 1091: 1092: 1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125: 1126: 1127: 1128: 1129: 1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 1216: 1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230: 1231: 1232: 1233: 1234: 1235: 1236: 1237: 1238: 1239: 1240: 1241: 1242: 1243: 1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282: 1283: 1284: 1285: 1286: 1287: 1288: 1289: 1290: 1291: 1292: 1293: 1294: 1295: 1296: 1297: 1298: 1299: 1300: 1301: 1302: 1303: 1304: 1305: 1306: 1307: 1308: 1309: 1310: 1311: 1312: 1313: 1314: 1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330: 1331: 1332: 1333: 1334: 1335: 1336: 1337: 1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346: 1347: 1348: 1349: 1350: 1351: 1352: 1353: 1354: 1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362: 1363: 1364: 1365: 1366: 1367: 1368: 1369: 1370: 1371: 1372: 
<?php

namespace sinacloud\sae;

/**
 * 新浪云 Storage PHP 客户端
 *
 * @copyright Copyright (c) 2015, SINA, All rights reserved.
 *
 * ```php
 * <?php
 * use sinacloud\sae\Storage as Storage;
 *
 * **类初始化**
 *
 * // 方法一:在新浪云运行环境中时可以不传认证信息,默认会从应用的环境变量中取
 * $s = new Storage();
 *
 * // 方法二:如果不在新浪云运行环境或者要连非本应用的 storage,需要传入所连应用的"应用名:应用 AccessKey"和"应用 SecretKey"
 * $s = new Storage("$AppName:$AccessKey", $SecretKey);
 *
 * **Bucket 操作**
 *
 * // 创建一个 Bucket test
 * $s->putBucket("test");
 *
 * // 获取 Bucket 列表
 * $s->listBuckets();
 *
 * // 获取 Bucket 列表及 Bucket 中 Object 数量和 Bucket 的大小
 * $s->listBuckets(true);
 *
 * // 获取 test 这个 Bucket 中的 Object 对象列表,默认返回前 1000 个,如果需要返回大于 1000 个 Object 的列表,可以通过 limit 参数来指定。
 * $s->getBucket("test");
 *
 * // 获取 test 这个 Bucket 中所有以 a/ 为前缀的 Objects 列表
 * $s->getBucket("test", 'a/');
 *
 * // 获取 test 这个 Bucket 中所有以 a/ 为前缀的 Objects 列表,只显示 a/N 这个 Object 之后的列表(不包含 a/N 这个 Object)。
 * $s->getBucket("test", 'a/', 'a/N');
 *
 * // Storage 也可以当成一个伪文件系统来使用,比如获取 a/ 目录下的 Object(不显示其下的子目录的具体 Object 名称,只显示目录名)
 * $s->getBucket("test", 'a/', null, 10000, '/');
 *
 * // 删除一个空的 Bucket test
 * $s->deleteBucket("test");
 *
 * **Object 上传操作**
 *
 * // 把 $_FILES 全局变量中的缓存文件上传到 test 这个 Bucket,设置此 Object 名为 1.txt
 * $s->putObjectFile($_FILES['uploaded']['tmp_name'], "test", "1.txt");
 *
 * // 把 $_FILES 全局变量中的缓存文件上传到 test 这个 Bucket,设置此 Object 名为 sae/1.txt
 * $s->putObjectFile($_FILES['uploaded']['tmp_name'], "test", "sae/1.txt");
 *
 * // 上传一个字符串到 test 这个 Bucket 中,设置此 Object 名为 string.txt,并且设置其 Content-type
 * $s->putObject("This is string.", "test", "string.txt", array(), array('Content-Type' => 'text/plain'));
 *
 * // 上传一个文件句柄(必须是 buffer 或者一个文件,文件会被自动 fclose 掉)到 test 这个 Bucket 中,设置此 Object 名为 file.txt
 * $s->putObject(Storage::inputResource(fopen($_FILES['uploaded']['tmp_name'], 'rb'), filesize($_FILES['uploaded']['tmp_name']), "test", "file.txt", Storage::ACL_PUBLIC_READ);
 *
 * **Object 下载操作**
 *
 * // 从 test 这个 Bucket 读取 Object 1.txt,输出为此次请求的详细信息,包括状态码和 1.txt 的内容等
 * var_dump($s->getObject("test", "1.txt"));
 *
 * // 从 test 这个 Bucket 读取 Object 1.txt,把 1.txt 的内容保存在 SAE_TMP_PATH 变量指定的 TmpFS 中,savefile.txt 为保存的文件名;SAE_TMP_PATH 路径具有写权限,用户可以往这个目录下写文件,但文件的生存周期等同于 PHP 请求,也就是当该 PHP 请求完成执行时,所有写入 SAE_TMP_PATH 的文件都会被销毁
 * $s->getObject("test", "1.txt", SAE_TMP_PATH."savefile.txt");
 *
 * // 从 test 这个 Bucket 读取 Object 1.txt,把 1.txt 的内容保存在打开的文件句柄中
 * $s->getObject("test", "1.txt", fopen(SAE_TMP_PATH."savefile.txt", 'wb'));
 *
 * **Object 删除操作**
 *
 * // 从 test 这个 Bucket 删除 Object 1.txt
 * $s->deleteObject("test", "1.txt");
 *
 * **Object 复制操作**
 *
 * // 把 test 这个 Bucket 的 Object 1.txt 内容复制到 newtest 这个 Bucket 的 Object 1.txt
 * $s->copyObject("test", "1.txt", "newtest", "1.txt");
 *
 * // 把 test 这个 Bucket 的 Object 1.txt 内容复制到 newtest 这个 Bucket 的 Object 1.txt,并设置 Object 的浏览器缓存过期时间为 10s 和 Content-Type 为 text/plain
 * $s->copyObject("test", "1.txt", "newtest", "1.txt", array('expires' => '10s'), array('Content-Type' => 'text/plain'));
 *
 * **生成一个外网能够访问的 url**
 *
 * // 为私有 Bucket test 中的 Object 1.txt 生成一个能够在外网用 GET 方法临时访问的 URL,次 URL 过期时间为 600s
 * $s->getTempUrl("test", "1.txt", "GET", 600);
 *
 * // 为 test 这个 Bucket 中的 Object 1.txt 生成一个能用 CDN 访问的 URL
 * $s->getCdnUrl("test", "1.txt");
 *
 * **调试模式**
 *
 * // 开启调试模式,出问题的时候方便定位问题,设置为 true 后遇到错误的时候会抛出异常而不是写一条 warning 信息到日志。
 * $s->setExceptions(true);
 * ?>
 * ```
 */

if (defined('SAE_APPNAME')) {
    define('DEFAULT_STORAGE_ENDPOINT', 'api.i.sinas3.com:81');
    define('DEFAULT_USE_SSL', false);
} else {
    define('DEFAULT_STORAGE_ENDPOINT', 'api.sinas3.com');
    define('DEFAULT_USE_SSL', true);
}

class Storage
{
    // ACL flags
    const ACL_PRIVATE = '';
    const ACL_PUBLIC_READ = '.r:*';

    private static $__accessKey = null;
    private static $__secretKey = null;
    private static $__account = null;

    /**
     * 默认使用的分隔符,getBucket() 等用到
     *
     * @var string
     * @access public
     * @static
     */
    public static $defDelimiter = null;

    public static $endpoint = DEFAULT_STORAGE_ENDPOINT;

    public static $proxy = null;

    /**
     * 使用 SSL 连接?
     *
     * @var bool
     * @access public
     * @static
     */
    public static $useSSL = DEFAULT_USE_SSL;

    /**
     * 是否验证 SSL 证书
     *
     * @var bool
     * @access public
     * @static
     */
    public static $useSSLValidation = false;

    /**
     * 使用的 SSL 版本
     *
     * @var const
     * @access public
     * @static
     */
    public static $useSSLVersion = 1;

    /**
     * 出现错误的时候是否使用 PHP Exception(默认使用 trigger_error 纪录错误)
     *
     * @var bool
     * @access public
     * @static
     */
    public static $useExceptions = false;

    /**
     * 构造函数
     *
     * @param string $accessKey 此处需要使用"应用名:Accesskey"
     * @param string $secretKey 应用 Secretkey
     * @param boolean $useSSL 是否使用 SSL
     * @param string $endpoint 新浪云 Storage 的 endpoint
     * @return void
     */
    public function __construct($accessKey = null, $secretKey = null,
            $useSSL = DEFAULT_USE_SSL, $endpoint = DEFAULT_STORAGE_ENDPOINT)
    {
        if ($accessKey !== null && $secretKey !== null) {
            self::setAuth($accessKey, $secretKey);
        } else if (defined('SAE_APPNAME')) {
            // We are in 新浪云运行环境
            self::setAuth(SAE_APPNAME.':'.SAE_ACCESSKEY, SAE_SECRETKEY);
        }
        self::$useSSL = $useSSL;
        self::$endpoint = $endpoint;
    }


    /**
     * 设置新浪云的 Storage 的 endpoint
     *
     * @param string $host 新浪云 Storage 的 hostname
     * @return void
     */
    public function setEndpoint($host)
    {
        self::$endpoint = $host;
    }


    /**
     * 设置访问的 Accesskey 和 Secretkey
     *
     * @param string $accessKey 此处需要使用"应用名:应用 Accesskey"
     * @param string $secretKey 应用 Secretkey
     * @return void
     */
    public static function setAuth($accessKey, $secretKey)
    {
        $e = explode(':', $accessKey);
        self::$__account = $e[0];
        self::$__accessKey = $e[1];
        self::$__secretKey = $secretKey;
    }


    public static function hasAuth() {
        return (self::$__accessKey !== null && self::$__secretKey !== null);
    }


    /**
     * 开启或者关闭 SSL
     *
     * @param boolean $enabled 是否启用 SSL
     * @param boolean $validate 是否验证 SSL 证书
     * @return void
     */
    public static function setSSL($enabled, $validate = true)
    {
        self::$useSSL = $enabled;
        self::$useSSLValidation = $validate;
    }


    /**
     * 设置代理信息
     *
     * @param string $host 代理的 hostname 和端口 (localhost:1234)
     * @param string $user 代理的 username
     * @param string $pass 代理的 password
     * @param constant $type CURL 代理类型
     * @return void
     */
    public static function setProxy($host, $user = null, $pass = null, $type = CURLPROXY_SOCKS5)
    {
        self::$proxy = array('host' => $host, 'type' => $type, 'user' => $user, 'pass' => $pass);
    }


    /**
     * 设置是否使用 PHP Exception,默认使用 trigger_error
     *
     * @param boolean $enabled Enable exceptions
     * @return void
     */
    public static function setExceptions($enabled = true)
    {
        self::$useExceptions = $enabled;
    }


    private static function __triggerError($message, $file, $line, $code = 0)
    {
        if (self::$useExceptions)
            throw new StorageException($message, $file, $line, $code);
        else
            trigger_error($message, E_USER_WARNING);
    }


    /**
     * 获取 bucket 列表
     *
     * @param boolean $detailed 设置为 true 时返回 bucket 的详细信息
     * @return array | false
     */
    public static function listBuckets($detailed = false)
    {
        $rest = new StorageRequest('GET', self::$__account, '', '', self::$endpoint);
        $rest->setParameter('format', 'json');
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 200)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::listBuckets(): [%s] %s", $rest->error['code'],
                $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        $buckets = json_decode($rest->body, True);
        if ($buckets === False) {
            self::__triggerError(sprintf("Storage::listBuckets(): invalid body: %s", $rest->body),
                __FILE__, __LINE__);
            return false;
        }

        if ($detailed) {
            return $buckets;
        }

        $results = array();
        foreach ($buckets as $b) $results[] = (string)$b['name'];
        return $results;
    }


    /**
     * 获取 bucket 中的 object 列表
     *
     * @param string $bucket Bucket 名称
     * @param string $prefix Object 名称的前缀
     * @param string $marker Marker (返回 marker 之后的 object 列表,不包含 marker)
     * @param string $limit 最大返回的 Object 数目
     * @param string $delimiter 分隔符
     * @return array | false
     */
    public static function getBucket($bucket, $prefix = null, $marker = null, $limit = 1000, $delimiter = null, $keyword = null)
    {
        $result = array();

        do {
            $rest = new StorageRequest('GET', self::$__account, $bucket, '', self::$endpoint);
            $rest->setParameter('format', 'json');
            if ($prefix !== null && $prefix !== '') $rest->setParameter('prefix', $prefix);
            if ($marker !== null && $marker !== '') $rest->setParameter('marker', $marker);
            if ($delimiter !== null && $delimiter !== '') $rest->setParameter('delimiter', $delimiter);
            if ($keyword !== null && $keyword !== '') $rest->setParameter('keyword', $keyword);
            else if (!empty(self::$defDelimiter)) $rest->setParameter('delimiter', self::$defDelimiter);
            if ($limit > 1000) {
                $max_keys = 1000;
            } else {
                $max_keys = $limit;
            }
            $rest->setParameter("limit", $max_keys);
            $limit -= 1000;
            $response = $rest->getResponse();
            if ($response->error === false && $response->code !== 200)
                $response->error = array('code' => $response->code, 'message' => 'Unexpected HTTP status');
            if ($response->error !== false)
            {
                self::__triggerError(sprintf("Storage::getBucket(): [%s] %s", $response->error['code'],
                    $response->error['message']), __FILE__, __LINE__);
                return false;
            }

            $objects = json_decode($response->body, True);
            if ($objects === False) {
                self::__triggerError(sprintf("Storage::getBucket(): invalid body: %s", $response->body),
                    __FILE__, __LINE__);
                return false;
            }

            if ($objects) {
                $result = array_merge($result, $objects);
                $marker = end($objects);
                $marker = $marker['name'];
            }
        } while ($objects && count($objects) == $max_keys && $limit > 0);

        return $result;
    }


    /**
     * 创建一个 Bucket
     *
     * @param string $bucket Bucket 名称
     * @param constant $acl Bucket 的 ACL
     * @param array $metaHeaders x-sws-container-meta-* header 数组
     * @return boolean
     */
    public static function putBucket($bucket, $acl = self::ACL_PRIVATE, $metaHeaders=array())
    {
        $rest = new StorageRequest('PUT', self::$__account, $bucket, '', self::$endpoint);
        if ($acl) {
            $rest->setSwsHeader('x-sws-container-read', $acl);
        }
        foreach ($metaHeaders as $k => $v) {
            $rest->setSwsHeader('x-sws-container-meta-'.$k, $v);
        }

        $rest = $rest->getResponse();

        if ($rest->error === false && ($rest->code !== 201 && $rest->code != 202 && $rest->code !== 204))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::putBucket({$bucket}, {$acl}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }

    /**
     * 获取一个 Bucket 的属性
     * @param string $bucket Bucket 名称
     * @param boolean $returnInfo 是否返回 Bucket 的信息
     * @return mixed
     */
    public static function getBucketInfo($bucket, $returnInfo=True) {
        $rest = new StorageRequest('HEAD', self::$__account, $bucket, '', self::$endpoint);
        $rest = $rest->getResponse();
        if ($rest->error === false && ($rest->code !== 204 && $rest->code !== 404))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::getBucketInfo({$bucket}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return $rest->code !== 404 ? $returnInfo ? $rest->headers : true : false;
    }

    /**
     * 修改一个 Bucket 的属性
     *
     * @param string $bucket Bucket 名称
     * @param constant $acl Bucket 的 ACL,null 表示不变
     * @param array $metaHeaders x-sws-container-meta-* header 数组
     * @return boolean
     */
    public static function postBucket($bucket, $acl = null, $metaHeaders=array())
    {
        $rest = new StorageRequest('POST', self::$__account, $bucket, '', self::$endpoint);
        if ($acl) {
            $rest->setSwsHeader('x-sws-container-read', $acl);
        }
        foreach ($metaHeaders as $k => $v) {
            $rest->setSwsHeader('x-sws-container-meta-'.$k, $v);
        }

        $rest = $rest->getResponse();

        if ($rest->error === false && ($rest->code !== 201 && $rest->code !== 204))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::postBucket({$bucket}, {$acl}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }

    /**
     * 删除一个空的 Bucket
     *
     * @param string $bucket Bucket 名称
     * @return boolean
     */
    public static function deleteBucket($bucket)
    {
        $rest = new StorageRequest('DELETE', self::$__account, $bucket, '', self::$endpoint);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 204)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::deleteBucket({$bucket}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }


    /**
     * 为本地文件路径创建一个可以用于 putObject() 上传的 array
     *
     * @param string $file 文件路径
     * @param mixed $md5sum Use MD5 hash (supply a string if you want to use your own)
     * @return array | false
     */
    public static function inputFile($file, $md5sum = false)
    {
        if (!file_exists($file) || !is_file($file) || !is_readable($file))
        {
            self::__triggerError('Storage::inputFile(): Unable to open input file: '.$file, __FILE__, __LINE__);
            return false;
        }
        clearstatcache(false, $file);
        return array('file' => $file, 'size' => filesize($file), 'md5sum' => $md5sum !== false ?
            (is_string($md5sum) ? $md5sum : base64_encode(md5_file($file, true))) : '');
    }


    /**
     * 为打开的文件句柄创建一个可以用于 putObject() 上传的 array
     *
     * @param string $resource Input resource to read from
     * @param integer $bufferSize Input byte size
     * @param string $md5sum MD5 hash to send (optional)
     * @return array | false
     */
    public static function inputResource(&$resource, $bufferSize = false, $md5sum = '')
    {
        if (!is_resource($resource) || (int)$bufferSize < 0)
        {
            self::__triggerError('Storage::inputResource(): Invalid resource or buffer size', __FILE__, __LINE__);
            return false;
        }

        // Try to figure out the bytesize
        if ($bufferSize === false)
        {
            if (fseek($resource, 0, SEEK_END) < 0 || ($bufferSize = ftell($resource)) === false)
            {
                self::__triggerError('Storage::inputResource(): Unable to obtain resource size', __FILE__, __LINE__);
                return false;
            }
            fseek($resource, 0);
        }

        $input = array('size' => $bufferSize, 'md5sum' => $md5sum);
        $input['fp'] =& $resource;
        return $input;
    }


    /**
     * 上传一个 object
     *
     * @param mixed $input Input data
     * @param string $bucket Bucket name
     * @param string $uri Object URI
     * @param array $metaHeaders x-sws-object-meta-* header 数组
     * @param array $requestHeaders Array of request headers or content type as a string
     * @return boolean
     */
    public static function putObject($input, $bucket, $uri, $metaHeaders = array(), $requestHeaders = array())
    {
        if ($input === false) return false;
        $rest = new StorageRequest('PUT', self::$__account, $bucket, $uri, self::$endpoint);

        if (!is_array($input)) $input = array(
            'data' => $input, 'size' => strlen($input),
            'md5sum' => base64_encode(md5($input, true))
        );

        // Data
        if (isset($input['fp']))
            $rest->fp =& $input['fp'];
        elseif (isset($input['file']))
            $rest->fp = @fopen($input['file'], 'rb');
        elseif (isset($input['data']))
            $rest->data = $input['data'];

        // Content-Length (required)
        if (isset($input['size']) && $input['size'] >= 0)
            $rest->size = $input['size'];
        else {
            if (isset($input['file'])) {
                clearstatcache(false, $input['file']);
                $rest->size = filesize($input['file']);
            }
            elseif (isset($input['data']))
                $rest->size = strlen($input['data']);
        }

        // Custom request headers (Content-Type, Content-Disposition, Content-Encoding)
        if (is_array($requestHeaders))
            foreach ($requestHeaders as $h => $v)
                strpos($h, 'x-') === 0 ? $rest->setSwsHeader($h, $v) : $rest->setHeader($h, $v);

        if (is_string($requestHeaders)) // Support for legacy contentType parameter
            $rest->setHeader('content-type', $requestHeaders);
        else if (!isset($requestHeaders['content-type']) && !isset($requestHeaders['Content-Type']))
            $rest->setHeader('content-type', self::__getMIMEType($uri));

        // We need to post with Content-Length and Content-Type, MD5 is optional
        if ($rest->size >= 0 && ($rest->fp !== false || $rest->data !== false))
        {
            if (isset($input['md5sum'])) $rest->setHeader('Content-MD5', $input['md5sum']);

            foreach ($metaHeaders as $h => $v) $rest->setSwsHeader('x-sws-object-meta-'.$h, $v);
            $rest->getResponse();
        } else
            $rest->response->error = array('code' => 0, 'message' => 'Missing input parameters');

        if ($rest->response->error === false && $rest->response->code !== 201)
            $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
        if ($rest->response->error !== false)
        {
            self::__triggerError(sprintf("Storage::putObject(): [%s] %s",
                $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }


    /**
     * Put an object from a file (legacy function)
     *
     * @param string $file Input file path
     * @param string $bucket Bucket name
     * @param string $uri Object URI
     * @param constant $acl ACL constant
     * @param array $metaHeaders Array of x-meta-* headers
     * @param string $contentType Content type
     * @return boolean
     */
    public static function putObjectFile($file, $bucket, $uri, $metaHeaders = array(), $contentType = null)
    {
        return self::putObject(self::inputFile($file), $bucket, $uri, $metaHeaders, $contentType);
    }


    /**
     * Put an object from a string (legacy function)
     *
     * @param string $string Input data
     * @param string $bucket Bucket name
     * @param string $uri Object URI
     * @param constant $acl ACL constant
     * @param array $metaHeaders Array of x-sws-meta-* headers
     * @param string $contentType Content type
     * @return boolean
     */
    public static function putObjectString($string, $bucket, $uri, $metaHeaders = array(), $contentType = false)
    {
        return self::putObject($string, $bucket, $uri, $metaHeaders, $contentType);
    }

    /**
     * 修改一个 Object 的属性
     *
     * @param string $bucket Bucket 名称
     * @param constant $uri Object 名称
     * @param array $metaHeaders x-sws-container-meta-* header 数组
     * @param array $requestHeaders 其它 header 属性
     * @return boolean
     */
    public static function postObject($bucket, $uri, $metaHeaders=array(), $requestHeaders=Array())
    {
        $rest = new StorageRequest('POST', self::$__account, $bucket, $uri, self::$endpoint);
        foreach ($metaHeaders as $k => $v) {
            $rest->setSwsHeader('x-sws-object-meta-'.$k, $v);
        }
        foreach ($requestHeaders as $k => $v) {
            $rest->setHeader('x-sws-object-meta-'.$k, $v);
        }

        $rest = $rest->getResponse();

        if ($rest->error === false && ($rest->code !== 202))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::postObject({$bucket}, {$uri}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }

    /**
     * 获取一个 Object 的内容
     *
     * @param string $bucket Bucket 名称
     * @param string $uri Object 名称
     * @param mixed $saveTo 文件保存到的文件名或者句柄
     * @return mixed 返回服务端返回的 response,其中 headers 为 Object 的属性信息,body 为 Object 的内容
     */
    public static function getObject($bucket, $uri, $saveTo = false)
    {
        $rest = new StorageRequest('GET', self::$__account, $bucket, $uri, self::$endpoint);
        if ($saveTo !== false)
        {
            if (is_resource($saveTo))
                $rest->fp =& $saveTo;
            else
                if (($rest->fp = @fopen($saveTo, 'wb')) !== false)
                    $rest->file = realpath($saveTo);
                else
                    $rest->response->error = array('code' => 0, 'message' => 'Unable to open save file for writing: '.$saveTo);
        }
        if ($rest->response->error === false) $rest->getResponse();

        if ($rest->response->error === false && $rest->response->code !== 200)
            $rest->response->error = array('code' => $rest->response->code, 'message' => 'Unexpected HTTP status');
        if ($rest->response->error !== false)
        {
            self::__triggerError(sprintf("Storage::getObject({$bucket}, {$uri}): [%s] %s",
                $rest->response->error['code'], $rest->response->error['message']), __FILE__, __LINE__);
            return false;
        }
        return $rest->response;
    }


    /**
     * 获取一个 Object 的信息
     *
     * @param string $bucket Bucket 名称
     * @param string $uri Object 名称
     * @param boolean $returnInfo 是否返回 Object 的详细信息
     * @return mixed | false
     */
    public static function getObjectInfo($bucket, $uri, $returnInfo = true)
    {
        $rest = new StorageRequest('HEAD', self::$__account, $bucket, $uri, self::$endpoint);
        $rest = $rest->getResponse();
        if ($rest->error === false && ($rest->code !== 200 && $rest->code !== 404))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::getObjectInfo({$bucket}, {$uri}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return $rest->code == 200 ? $returnInfo ? $rest->headers : true : false;
    }


    /**
     * 从一个 Bucket 复制一个 Object 到另一个 Bucket
     *
     * @param string $srcBucket 源 Bucket 名称
     * @param string $srcUri 源 Object 名称
     * @param string $bucket 目标 Bucket 名称
     * @param string $uri 目标 Object 名称
     * @param array $metaHeaders Optional array of x-sws-meta-* headers
     * @param array $requestHeaders Optional array of request headers (content type, disposition, etc.)
     * @return mixed | false
     */
    public static function copyObject($srcBucket, $srcUri, $bucket, $uri, $metaHeaders = array(), $requestHeaders = array())
    {
        $rest = new StorageRequest('PUT', self::$__account, $bucket, $uri, self::$endpoint);
        $rest->setHeader('Content-Length', 0);
        foreach ($requestHeaders as $h => $v)
            strpos($h, 'x-sws-') === 0 ? $rest->setSwsHeader($h, $v) : $rest->setHeader($h, $v);
        foreach ($metaHeaders as $h => $v) $rest->setSwsHeader('x-sws-object-meta-'.$h, $v);
        $rest->setSwsHeader('x-sws-copy-from', sprintf('/%s/%s', $srcBucket, rawurlencode($srcUri)));
        $rest->setSwsHeader('x-sws-newest', '1');

        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 201)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::copyObject({$srcBucket}, {$srcUri}, {$bucket}, {$uri}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return isset($rest->headers['time'], $rest->headers['hash']) ? $rest->headers : false;
    }


    /**
     * Set object or bucket Access Control Policy
     *
     * @param string $bucket Bucket name
     * @param string $uri Object URI
     * @param array $acp Access Control Policy Data (same as the data returned from getAccessControlPolicy)
     * @return boolean
     */
    public static function setAccessControlPolicy($bucket, $uri = '', $acp = array())
    {
    }


    /**
     * 删除一个 Object
     *
     * @param string $bucket Bucket 名称
     * @param string $uri Object 名称
     * @return boolean
     */
    public static function deleteObject($bucket, $uri)
    {
        $rest = new StorageRequest('DELETE', self::$__account, $bucket, $uri, self::$endpoint);
        $rest = $rest->getResponse();
        if ($rest->error === false && $rest->code !== 204)
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::deleteObject(): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }


    /**
     * 获取一个 Object 的外网直接访问 URL
     *
     * @param string $bucket Bucket 名称
     * @param string $uri Object 名称
     * @return string
     */
    public static function getUrl($bucket, $uri)
    {
        return "http://" . self::$__account . '-' . $bucket . '.stor.sinaapp.com/' . self::__encodeURI($uri);
    }

     /**
     * 获取一个 Object 的外网临时访问 URL
     *
     * @param string $bucket Bucket 名称
     * @param string $uri Object 名称
     * @param string $method Http 请求的方法,有 GET, PUT, DELETE 等
     * @param int    $seconds 设置这个此 URL 的过期时间,单位是秒
     */
    public static function getTempUrl($bucket, $uri, $method, $seconds)
    {
        $expires = (int)(time() + $seconds);
        $path = "/v1/SAE_" . self::$__account . "/" . $bucket . "/" . $uri;
        $hmac_body = $method . "\n" . $expires . "\n" . $path;
        $sig = hash_hmac('sha1', $hmac_body, self::$__secretKey);
        $parameter = http_build_query(array("temp_url_sig" => $sig, "temp_url_expires" => $expires));
        return "http://" . self::$__account . '-' . $bucket . '.stor.sinaapp.com/' . self::__encodeURI($uri) . "?" . $parameter;
    }

    /**
     * 获取一个 Object 的 CDN 访问 URL
     * @param string $bucket Bucket 名称
     * @param string $uri Object 名称
     * @return string
     */
    public static function getCdnUrl($bucket, $uri)
    {
        return "http://". self::$__account . '.sae.sinacn.com/.app-stor/' . $bucket . '/' . self::__encodeURI($uri);
    }

    /**
     * 修改账户的属性(for internal use onley)
     *
     * @param array $metaHeaders x-sws-account-meta-* header 数组
     * @return boolean
     */
    public static function postAccount($metaHeaders=array())
    {
        $rest = new StorageRequest('POST', self::$__account, '', '', self::$endpoint);
        foreach ($metaHeaders as $k => $v) {
            $rest->setSwsHeader('x-sws-account-meta-'.$k, $v);
        }

        $rest = $rest->getResponse();

        if ($rest->error === false && ($rest->code !== 201 && $rest->code !== 204))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::postAccount({$bucket}, {$acl}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return true;
    }

    /**
     * 获取账户的属性(for internal use only)
     *
     * @param string $bucket Bucket 名称
     * @return mixed
     */
    public static function getAccountInfo() {
        $rest = new StorageRequest('HEAD', self::$__account, '', '', self::$endpoint);
        $rest = $rest->getResponse();
        if ($rest->error === false && ($rest->code !== 204 && $rest->code !== 404))
            $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
        if ($rest->error !== false)
        {
            self::__triggerError(sprintf("Storage::getAccountInfo({$bucket}): [%s] %s",
                $rest->error['code'], $rest->error['message']), __FILE__, __LINE__);
            return false;
        }
        return $rest->code !== 404 ? $rest->headers : false;
    }


    private static function __getMIMEType(&$file)
    {
        static $exts = array(
            'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif',
            'png' => 'image/png', 'ico' => 'image/x-icon', 'pdf' => 'application/pdf',
            'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'svg' => 'image/svg+xml',
            'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash',
            'zip' => 'application/zip', 'gz' => 'application/x-gzip',
            'tar' => 'application/x-tar', 'bz' => 'application/x-bzip',
            'bz2' => 'application/x-bzip2',  'rar' => 'application/x-rar-compressed',
            'exe' => 'application/x-msdownload', 'msi' => 'application/x-msdownload',
            'cab' => 'application/vnd.ms-cab-compressed', 'txt' => 'text/plain',
            'asc' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html',
            'css' => 'text/css', 'js' => 'text/javascript',
            'xml' => 'text/xml', 'xsl' => 'application/xsl+xml',
            'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
            'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
            'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php'
        );

        $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (isset($exts[$ext])) return $exts[$ext];

        // Use fileinfo if available
        if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
            ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false)
        {
            if (($type = finfo_file($finfo, $file)) !== false)
            {
                // Remove the charset and grab the last content-type
                $type = explode(' ', str_replace('; charset=', ';charset=', $type));
                $type = array_pop($type);
                $type = explode(';', $type);
                $type = trim(array_shift($type));
            }
            finfo_close($finfo);
            if ($type !== false && strlen($type) > 0) return $type;
        }

        return 'application/octet-stream';
    }

    private static function __encodeURI($uri)
    {
        return str_replace('%2F', '/', rawurlencode($uri));
    }

    public static function __getTime()
    {
        return time() + self::$__timeOffset;
    }


    public static function __getSignature($string)
    {
        //var_dump("sign:", $string);
        return 'SWS '.self::$__accessKey.':'.self::__getHash($string);
    }


    private static function __getHash($string)
    {
        return base64_encode(extension_loaded('hash') ?
            hash_hmac('sha1', $string, self::$__secretKey, true) : pack('H*', sha1(
                (str_pad(self::$__secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
                pack('H*', sha1((str_pad(self::$__secretKey, 64, chr(0x00)) ^
                (str_repeat(chr(0x36), 64))) . $string)))));
    }

}

/**
 * @ignore
 */
final class StorageRequest
{
    private $endpoint;
    private $verb;
    private $uri;
    private $parameters = array();
    private $swsHeaders = array();
    private $headers = array(
        'Host' => '', 'Date' => '', 'Content-MD5' => ''
    );

    public $fp = false;
    public $size = 0;
    public $data = false;

    public $response;


    function __construct($verb, $account, $bucket = '', $uri = '', $endpoint = DEFAULT_STORAGE_ENDPOINT)
    {
        $this->endpoint = $endpoint;
        $this->verb = $verb;

        $this->uri = "/v1/SAE_" . rawurlencode($account);
        $this->resource = "/$account";
        if ($bucket !== '') {
            $this->uri = $this->uri . '/' . rawurlencode($bucket);
            $this->resource = $this->resource . '/'. $bucket;
        }
        if ($uri !== '') {
            $this->uri .= '/'.str_replace('%2F', '/', rawurlencode($uri));
            $this->resource = $this->resource . '/'. str_replace(' ', '%20', $uri);
        }

        $this->headers['Host'] = $this->endpoint;
        $this->headers['Date'] = gmdate('D, d M Y H:i:s T');
        $this->response = new \STDClass;
        $this->response->error = false;
        $this->response->body = null;
        $this->response->headers = array();
    }


    public function setParameter($key, $value)
    {
        $this->parameters[$key] = $value;
    }


    public function setHeader($key, $value)
    {
        $this->headers[$key] = $value;
    }


    public function setSwsHeader($key, $value)
    {
        $this->swsHeaders[$key] = $value;
    }


    public function getResponse()
    {
        $query = '';
        if (sizeof($this->parameters) > 0)
        {
            $query = substr($this->uri, -1) !== '?' ? '?' : '&';
            foreach ($this->parameters as $var => $value)
                if ($value == null || $value == '') $query .= $var.'&';
                else $query .= $var.'='.rawurlencode($value).'&';
            $query = substr($query, 0, -1);
            $this->uri .= $query;
            $this->resource .= $query;
        }
        $url = (Storage::$useSSL ? 'https://' : 'http://') . ($this->headers['Host'] !== '' ? $this->headers['Host'] : $this->endpoint) . $this->uri;

        //var_dump('uri: ' . $this->uri, 'url: ' . $url, 'resource: ' . $this->resource);

        // Basic setup
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_USERAGENT, 'Storage/php');

        if (Storage::$useSSL)
        {
            // Set protocol version
            curl_setopt($curl, CURLOPT_SSLVERSION, Storage::$useSSLVersion);

            // SSL Validation can now be optional for those with broken OpenSSL installations
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, Storage::$useSSLValidation ? 2 : 0);
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, Storage::$useSSLValidation ? 1 : 0);
        }

        curl_setopt($curl, CURLOPT_URL, $url);

        if (Storage::$proxy != null && isset(Storage::$proxy['host']))
        {
            curl_setopt($curl, CURLOPT_PROXY, Storage::$proxy['host']);
            curl_setopt($curl, CURLOPT_PROXYTYPE, Storage::$proxy['type']);
            if (isset(Storage::$proxy['user'], Storage::$proxy['pass']) && Storage::$proxy['user'] != null && Storage::$proxy['pass'] != null)
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, sprintf('%s:%s', Storage::$proxy['user'], Storage::$proxy['pass']));
        }

        // Headers
        $headers = array(); $sae = array();
        foreach ($this->swsHeaders as $header => $value)
            if (strlen($value) > 0) $headers[] = $header.': '.$value;
        foreach ($this->headers as $header => $value)
            if (strlen($value) > 0) $headers[] = $header.': '.$value;

        foreach ($this->swsHeaders as $header => $value)
            if (strlen($value) > 0) $sae[] = strtolower($header).':'.$value;

        if (sizeof($sae) > 0)
        {
            usort($sae, array(&$this, '__sortMetaHeadersCmp'));
            $sae= "\n".implode("\n", $sae);
        } else $sae = '';

        if (Storage::hasAuth())
        {
            $headers[] = 'Authorization: ' . Storage::__getSignature(
                $this->verb."\n".
                $this->headers['Date'].$sae."\n".
                $this->resource
            );
        }

        //var_dump("headers:", $headers);

        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
        curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
        curl_setopt($curl, CURLOPT_HEADERFUNCTION, array(&$this, '__responseHeaderCallback'));

        // Request types
        switch ($this->verb)
        {
        case 'GET': break;
        case 'PUT': case 'POST':
            if ($this->fp !== false)
            {
                curl_setopt($curl, CURLOPT_PUT, true);
                curl_setopt($curl, CURLOPT_INFILE, $this->fp);
                if ($this->size >= 0)
                    curl_setopt($curl, CURLOPT_INFILESIZE, $this->size);
            }
            elseif ($this->data !== false)
            {
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
                curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
            }
            else
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
            break;
        case 'HEAD':
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
            curl_setopt($curl, CURLOPT_NOBODY, true);
            break;
        case 'DELETE':
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
            break;
        default: break;
        }

        // Execute, grab errors
        if (curl_exec($curl))
            $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        else
            $this->response->error = array(
                'code' => curl_errno($curl),
                'message' => curl_error($curl),
            );

        @curl_close($curl);

        // Clean up file resources
        if ($this->fp !== false && is_resource($this->fp)) fclose($this->fp);

        //var_dump("response:", $this->response);
        return $this->response;
    }


    private function __sortMetaHeadersCmp($a, $b)
    {
        $lenA = strpos($a, ':');
        $lenB = strpos($b, ':');
        $minLen = min($lenA, $lenB);
        $ncmp = strncmp($a, $b, $minLen);
        if ($lenA == $lenB) return $ncmp;
        if (0 == $ncmp) return $lenA < $lenB ? -1 : 1;
        return $ncmp;
    }


    private function __responseWriteCallback(&$curl, &$data)
    {
        if (in_array($this->response->code, array(200, 206)) && $this->fp !== false)
            return fwrite($this->fp, $data);
        else
            $this->response->body .= $data;
        return strlen($data);
    }


    private function __responseHeaderCallback($curl, $data)
    {
        if (($strlen = strlen($data)) <= 2) return $strlen;
        if (substr($data, 0, 4) == 'HTTP')
            $this->response->code = (int)substr($data, 9, 3);
        else
        {
            $data = trim($data);
            if (strpos($data, ': ') === false) return $strlen;
            list($header, $value) = explode(': ', $data, 2);
            if ($header == 'Last-Modified')
                $this->response->headers['time'] = strtotime($value);
            elseif ($header == 'Date')
                $this->response->headers['date'] = strtotime($value);
            elseif ($header == 'Content-Length')
                $this->response->headers['size'] = (int)$value;
            elseif ($header == 'Content-Type')
                $this->response->headers['type'] = $value;
            elseif ($header == 'ETag' || $header == 'Etag')
                $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value;
            elseif (preg_match('/^x-sws-(?:account|container|object)-read$/i', $header))
                $this->response->headers['acl'] = $value;
            elseif (preg_match('/^x-sws-(?:account|container|object)-meta-(.*)$/i', $header))
                $this->response->headers[strtolower($header)] = $value;
            elseif (preg_match('/^x-sws-(?:account|container|object)-(.*)$/i', $header, $m))
                $this->response->headers[strtolower($m[1])] = $value;
        }
        return $strlen;
    }

}

/**
 * Storage 异常类
 */
class StorageException extends \Exception {
    /**
     * 构造函数
     *
     * @param string $message 异常信息
     * @param string $file 抛出异常的文件
     * @param string $line 抛出异常的代码行
     * @param int $code 异常码
     */
    function __construct($message, $file, $line, $code = 0)
    {
        parent::__construct($message, $code);
        $this->file = $file;
        $this->line = $line;
    }
}

/**
 * A PHP wrapper for Storage
 *
 * @ignore
 */
final class StorageWrapper extends Storage {
    private $position = 0, $mode = '', $buffer;

    public function url_stat($path, $flags) {
        self::__getURL($path);
        return (($info = self::getObjectInfo($this->url['host'], $this->url['path'])) !== false) ?
            array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']) : false;
    }

    public function unlink($path) {
        self::__getURL($path);
        return self::deleteObject($this->url['host'], $this->url['path']);
    }

    public function mkdir($path, $mode, $options) {
        self::__getURL($path);
        return self::putBucket($this->url['host'], self::__translateMode($mode));
    }

    public function rmdir($path) {
        self::__getURL($path);
        return self::deleteBucket($this->url['host']);
    }

    public function dir_opendir($path, $options) {
        self::__getURL($path);
        if (($contents = self::getBucket($this->url['host'], $this->url['path'])) !== false) {
            $pathlen = strlen($this->url['path']);
            if (substr($this->url['path'], -1) == '/') $pathlen++;
            $this->buffer = array();
            foreach ($contents as $file) {
                if ($pathlen > 0) $file['name'] = substr($file['name'], $pathlen);
                $this->buffer[] = $file;
            }
            return true;
        }
        return false;
    }

    public function dir_readdir() {
        return (isset($this->buffer[$this->position])) ? $this->buffer[$this->position++]['name'] : false;
    }

    public function dir_rewinddir() {
        $this->position = 0;
    }

    public function dir_closedir() {
        $this->position = 0;
        unset($this->buffer);
    }

    public function stream_close() {
        if ($this->mode == 'w') {
            self::putObject($this->buffer, $this->url['host'], $this->url['path']);
        }
        $this->position = 0;
        unset($this->buffer);
    }

    public function stream_stat() {
        if (is_object($this->buffer) && isset($this->buffer->headers))
            return array(
                'size' => $this->buffer->headers['size'],
                'mtime' => $this->buffer->headers['time'],
                'ctime' => $this->buffer->headers['time']
            );
        elseif (($info = self::getObjectInfo($this->url['host'], $this->url['path'])) !== false)
            return array('size' => $info['size'], 'mtime' => $info['time'], 'ctime' => $info['time']);
        return false;
    }

    public function stream_flush() {
        $this->position = 0;
        return true;
    }

    public function stream_open($path, $mode, $options, &$opened_path) {
        if (!in_array($mode, array('r', 'rb', 'w', 'wb'))) return false; // Mode not supported
        $this->mode = substr($mode, 0, 1);
        self::__getURL($path);
        $this->position = 0;
        if ($this->mode == 'r') {
            if (($this->buffer = self::getObject($this->url['host'], $this->url['path'])) !== false) {
                if (is_object($this->buffer->body)) $this->buffer->body = (string)$this->buffer->body;
            } else return false;
        }
        return true;
    }

    public function stream_read($count) {
        if ($this->mode !== 'r' && $this->buffer !== false) return false;
        $data = substr(is_object($this->buffer) ? $this->buffer->body : $this->buffer, $this->position, $count);
        $this->position += strlen($data);
        return $data;
    }

    public function stream_write($data) {
        if ($this->mode !== 'w') return 0;
        $left = substr($this->buffer, 0, $this->position);
        $right = substr($this->buffer, $this->position + strlen($data));
        $this->buffer = $left . $data . $right;
        $this->position += strlen($data);
        return strlen($data);
    }

    public function stream_tell() {
        return $this->position;
    }

    public function stream_eof() {
        return $this->position >= strlen(is_object($this->buffer) ? $this->buffer->body : $this->buffer);
    }

    public function stream_seek($offset, $whence) {
        switch ($whence) {
        case SEEK_SET:
            if ($offset < strlen($this->buffer->body) && $offset >= 0) {
                $this->position = $offset;
                return true;
            } else return false;
            break;
        case SEEK_CUR:
            if ($offset >= 0) {
                $this->position += $offset;
                return true;
            } else return false;
            break;
        case SEEK_END:
            $bytes = strlen($this->buffer->body);
            if ($bytes + $offset >= 0) {
                $this->position = $bytes + $offset;
                return true;
            } else return false;
            break;
        default: return false;
        }
    }

    private function __getURL($path) {
        $this->url = parse_url($path);
        if (!isset($this->url['scheme']) || $this->url['scheme'] !== 'storage') return $this->url;
        if (isset($this->url['user'], $this->url['pass'])) self::setAuth($this->url['user'], $this->url['pass']);
        $this->url['path'] = isset($this->url['path']) ? substr($this->url['path'], 1) : '';
    }

    private function __translateMode($mode) {
        $acl = self::ACL_PRIVATE;
        if (($mode & 0x0020) || ($mode & 0x0004))
            $acl = self::ACL_PUBLIC_READ;
        // You probably don't want to enable public write access
        if (($mode & 0x0010) || ($mode & 0x0008) || ($mode & 0x0002) || ($mode & 0x0001))
            $acl = self::ACL_PUBLIC_READ; //$acl = self::ACL_PUBLIC_READ_WRITE;
        return $acl;
    }
} stream_wrapper_register('storage', 'sinacloud\sae\StorageWrapper');
API documentation generated by ApiGen