113 parent::__construct( $config );
115 $this->swiftAuthUrl = $config[
'swiftAuthUrl'];
116 $this->swiftUser = $config[
'swiftUser'];
117 $this->swiftKey = $config[
'swiftKey'];
119 $this->authTTL = isset( $config[
'swiftAuthTTL'] )
120 ? $config[
'swiftAuthTTL']
122 $this->swiftTempUrlKey = isset( $config[
'swiftTempUrlKey'] )
123 ? $config[
'swiftTempUrlKey']
125 $this->swiftStorageUrl = isset( $config[
'swiftStorageUrl'] )
126 ? $config[
'swiftStorageUrl']
128 $this->shardViaHashLevels = isset( $config[
'shardViaHashLevels'] )
129 ? $config[
'shardViaHashLevels']
131 $this->rgwS3AccessKey = isset( $config[
'rgwS3AccessKey'] )
132 ? $config[
'rgwS3AccessKey']
134 $this->rgwS3SecretKey = isset( $config[
'rgwS3SecretKey'] )
135 ? $config[
'rgwS3SecretKey']
140 if ( isset( $config[
'wanCache'] ) && $config[
'wanCache'] instanceof
WANObjectCache ) {
141 $this->memCache = $config[
'wanCache'];
146 if ( !empty( $config[
'cacheAuthInfo'] ) && isset( $config[
'srvCache'] ) ) {
147 $this->srvCache = $config[
'srvCache'];
151 $this->readUsers = isset( $config[
'readUsers'] )
152 ? $config[
'readUsers']
154 $this->writeUsers = isset( $config[
'writeUsers'] )
155 ? $config[
'writeUsers']
157 $this->secureReadUsers = isset( $config[
'secureReadUsers'] )
158 ? $config[
'secureReadUsers']
160 $this->secureWriteUsers = isset( $config[
'secureWriteUsers'] )
161 ? $config[
'secureWriteUsers']
171 if ( !mb_check_encoding( $relStoragePath,
'UTF-8' ) ) {
173 } elseif ( strlen( rawurlencode( $relStoragePath ) ) > 1024 ) {
177 return $relStoragePath;
182 if ( $rel ===
null ) {
197 if ( !isset(
$params[
'headers'] ) ) {
202 unset( $headers[
'content-type' ] );
220 return isset(
$params[
'headers'] )
235 if ( preg_match(
'/^content-length$/',
$name ) ) {
237 } elseif ( preg_match(
'/^(x-)?content-/',
$name ) ) {
239 } elseif ( preg_match(
'/^content-(disposition)/',
$name ) ) {
244 if ( isset( $headers[
'content-disposition'] ) ) {
247 foreach ( explode(
';', $headers[
'content-disposition'] )
as $part ) {
248 $part = trim( $part );
249 $new = ( $disposition ===
'' ) ? $part :
"{$disposition};{$part}";
250 if ( strlen( $new ) <= 255 ) {
256 $headers[
'content-disposition'] = $disposition;
270 if ( strpos(
$name,
'x-object-meta-' ) === 0 ) {
285 $metadata[substr(
$name, strlen(
'x-object-meta-' ) )] =
$value;
295 if ( $dstRel ===
null ) {
301 $sha1Hash = Wikimedia\base_convert( sha1(
$params[
'content'] ), 16, 36, 31 );
302 $contentType = isset(
$params[
'headers'][
'content-type'] )
303 ?
$params[
'headers'][
'content-type']
308 'url' => [ $dstCont, $dstRel ],
310 'content-length' => strlen(
$params[
'content'] ),
311 'etag' => md5(
$params[
'content'] ),
312 'content-type' => $contentType,
313 'x-object-meta-sha1base36' => $sha1Hash
318 $method = __METHOD__;
320 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
321 if ( $rcode === 201 ) {
323 } elseif ( $rcode === 412 ) {
331 if ( !empty(
$params[
'async'] ) ) {
344 if ( $dstRel ===
null ) {
350 Wikimedia\suppressWarnings();
351 $sha1Hash = sha1_file(
$params[
'src'] );
352 Wikimedia\restoreWarnings();
353 if ( $sha1Hash ===
false ) {
358 $sha1Hash = Wikimedia\base_convert( $sha1Hash, 16, 36, 31 );
359 $contentType = isset(
$params[
'headers'][
'content-type'] )
360 ?
$params[
'headers'][
'content-type']
363 $handle = fopen(
$params[
'src'],
'rb' );
364 if ( $handle ===
false ) {
372 'url' => [ $dstCont, $dstRel ],
374 'content-length' => filesize(
$params[
'src'] ),
375 'etag' => md5_file(
$params[
'src'] ),
376 'content-type' => $contentType,
377 'x-object-meta-sha1base36' => $sha1Hash
382 $method = __METHOD__;
384 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
385 if ( $rcode === 201 ) {
387 } elseif ( $rcode === 412 ) {
395 $opHandle->resourcesToClose[] = $handle;
397 if ( !empty(
$params[
'async'] ) ) {
410 if ( $srcRel ===
null ) {
417 if ( $dstRel ===
null ) {
425 'url' => [ $dstCont, $dstRel ],
427 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
428 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
432 $method = __METHOD__;
434 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
435 if ( $rcode === 201 ) {
437 } elseif ( $rcode === 404 ) {
445 if ( !empty(
$params[
'async'] ) ) {
458 if ( $srcRel ===
null ) {
465 if ( $dstRel ===
null ) {
474 'url' => [ $dstCont, $dstRel ],
476 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
477 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
481 if (
"{$srcCont}/{$srcRel}" !==
"{$dstCont}/{$dstRel}" ) {
483 'method' =>
'DELETE',
484 'url' => [ $srcCont, $srcRel ],
489 $method = __METHOD__;
491 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
492 if (
$request[
'method'] ===
'PUT' && $rcode === 201 ) {
494 } elseif (
$request[
'method'] ===
'DELETE' && $rcode === 204 ) {
496 } elseif ( $rcode === 404 ) {
504 if ( !empty(
$params[
'async'] ) ) {
517 if ( $srcRel ===
null ) {
524 'method' =>
'DELETE',
525 'url' => [ $srcCont, $srcRel ],
529 $method = __METHOD__;
531 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
532 if ( $rcode === 204 ) {
534 } elseif ( $rcode === 404 ) {
535 if ( empty(
$params[
'ignoreMissingSource'] ) ) {
544 if ( !empty(
$params[
'async'] ) ) {
557 if ( $srcRel ===
null ) {
564 $stat = $this->
getFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
565 if ( $stat && !isset( $stat[
'xattr'] ) ) {
566 $stat = $this->
doGetFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
577 $metaHdrs[
"x-object-meta-$name"] =
$value;
579 $customHdrs = $this->
sanitizeHdrs( $params ) + $stat[
'xattr'][
'headers'];
583 'url' => [ $srcCont, $srcRel ],
584 'headers' => $metaHdrs + $customHdrs
587 $method = __METHOD__;
589 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
590 if ( $rcode === 202 ) {
592 } elseif ( $rcode === 404 ) {
600 if ( !empty(
$params[
'async'] ) ) {
614 if ( is_array( $stat ) ) {
616 } elseif ( $stat ===
null ) {
617 $status->fatal(
'backend-fail-internal', $this->
name );
618 $this->logger->error( __METHOD__ .
': cannot get container stat' );
624 if ( $stat ===
false ) {
634 if ( empty(
$params[
'noAccess'] ) ) {
639 if ( is_array( $stat ) ) {
640 $readUsers = array_merge( $this->secureReadUsers, [ $this->swiftUser ] );
641 $writeUsers = array_merge( $this->secureWriteUsers, [ $this->swiftUser ] );
648 } elseif ( $stat ===
false ) {
651 $status->fatal(
'backend-fail-internal', $this->
name );
652 $this->logger->error( __METHOD__ .
': cannot get container stat' );
662 if ( is_array( $stat ) ) {
663 $readUsers = array_merge( $this->readUsers, [ $this->swiftUser,
'.r:*' ] );
664 $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] );
672 } elseif ( $stat ===
false ) {
675 $status->fatal(
'backend-fail-internal', $this->
name );
676 $this->logger->error( __METHOD__ .
': cannot get container stat' );
692 if ( $stat ===
false ) {
694 } elseif ( !is_array( $stat ) ) {
695 $status->fatal(
'backend-fail-internal', $this->
name );
696 $this->logger->error( __METHOD__ .
': cannot get container stat' );
702 if ( $stat[
'count'] == 0 ) {
715 return reset( $stats );
732 return $timestamp->getTimestamp( $format );
733 }
catch ( Exception
$e ) {
746 if ( isset( $objHdrs[
'x-object-meta-sha1base36'] ) ) {
752 $this->logger->error( __METHOD__ .
": {path} was not stored with SHA-1 metadata.",
753 [
'path' =>
$path ] );
755 $objHdrs[
'x-object-meta-sha1base36'] =
false;
773 $hash = $tmpFile->getSha1Base36();
774 if ( $hash !==
false ) {
775 $objHdrs[
'x-object-meta-sha1base36'] = $hash;
777 $postHeaders[
'x-object-meta-sha1base36'] = $hash;
779 list( $rcode ) = $this->
http->run( [
781 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
784 if ( $rcode >= 200 && $rcode <= 299 ) {
793 $this->logger->error( __METHOD__ .
': unable to set SHA-1 metadata for {path}',
794 [
'path' =>
$path ] );
804 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
811 if ( $srcRel ===
null || !$auth ) {
812 $contents[
$path] =
false;
816 $handle = fopen(
'php://temp',
'wb' );
820 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
826 $contents[
$path] =
false;
829 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
830 $reqs = $this->
http->runMulti( $reqs, $opts );
831 foreach ( $reqs
as $path => $op ) {
832 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
833 if ( $rcode >= 200 && $rcode <= 299 ) {
834 rewind( $op[
'stream'] );
835 $contents[
$path] = stream_get_contents( $op[
'stream'] );
836 } elseif ( $rcode === 404 ) {
837 $contents[
$path] =
false;
839 $this->
onError(
null, __METHOD__,
840 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
842 fclose( $op[
'stream'] );
849 $prefix = ( $dir ==
'' ) ?
null :
"{$dir}/";
852 return ( count(
$status->value ) ) > 0;
893 if ( $after === INF ) {
899 $prefix = ( $dir ==
'' ) ?
null :
"{$dir}/";
901 if ( !empty(
$params[
'topOnly'] ) ) {
907 foreach ( $objects
as $object ) {
908 if ( substr( $object, -1 ) ===
'/' ) {
914 $getParentDir =
function (
$path ) {
919 $lastDir = $getParentDir( $after );
928 foreach ( $objects
as $object ) {
929 $objectDir = $getParentDir( $object );
931 if ( $objectDir !==
false && $objectDir !== $dir ) {
936 if ( strcmp( $objectDir, $lastDir ) > 0 ) {
939 $dirs[] =
"{$pDir}/";
940 $pDir = $getParentDir( $pDir );
941 }
while ( $pDir !==
false
942 && strcmp( $pDir, $lastDir ) > 0
943 && strlen( $pDir ) > strlen( $dir )
946 $lastDir = $objectDir;
951 if ( count( $objects ) < $limit ) {
954 $after = end( $objects );
973 if ( $after === INF ) {
979 $prefix = ( $dir ==
'' ) ?
null :
"{$dir}/";
982 if ( !empty(
$params[
'topOnly'] ) ) {
983 if ( !empty(
$params[
'adviseStat'] ) ) {
990 if ( !empty(
$params[
'adviseStat'] ) ) {
1006 if ( count( $objects ) < $limit ) {
1009 $after = end( $objects );
1010 $after = is_object( $after ) ? $after->name : $after;
1027 foreach ( $objects
as $object ) {
1028 if ( is_object( $object ) ) {
1029 if ( isset( $object->subdir ) || !isset( $object->name ) ) {
1035 'size' => (int)$object->bytes,
1038 'md5' => ctype_xdigit( $object->hash ) ? $object->hash :
null,
1041 $names[] = [ $object->name, $stat ];
1042 } elseif ( substr( $object, -1 ) !==
'/' ) {
1044 $names[] = [ $object, null ];
1058 $this->cheapCache->set(
$path,
'stat', $val );
1064 if ( !isset( $stat[
'xattr'] ) ) {
1070 return $stat[
'xattr'];
1079 if ( !isset( $stat[
'sha1'] ) ) {
1085 return $stat[
'sha1'];
1097 if ( $srcRel ===
null ) {
1126 if ( empty(
$params[
'allowOB'] ) ) {
1128 call_user_func( $this->obResetFunc );
1131 $handle = fopen(
'php://output',
'wb' );
1132 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1134 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1137 'stream' => $handle,
1138 'flags' => [
'relayResponseHeaders' => empty(
$params[
'headless'] ) ]
1141 if ( $rcode >= 200 && $rcode <= 299 ) {
1143 } elseif ( $rcode === 404 ) {
1163 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
1170 if ( $srcRel ===
null || !$auth ) {
1171 $tmpFiles[
$path] =
null;
1179 $handle = fopen( $tmpFile->getPath(),
'wb' );
1183 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1186 'stream' => $handle,
1192 $tmpFiles[
$path] = $tmpFile;
1195 $isLatest = ( $this->isRGW || !empty(
$params[
'latest'] ) );
1196 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1197 $reqs = $this->
http->runMulti( $reqs, $opts );
1198 foreach ( $reqs
as $path => $op ) {
1199 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
1200 fclose( $op[
'stream'] );
1201 if ( $rcode >= 200 && $rcode <= 299 ) {
1202 $size = $tmpFiles[
$path] ? $tmpFiles[
$path]->getSize() : 0;
1204 if ( $size != $rhdrs[
'content-length'] ) {
1205 $tmpFiles[
$path] =
null;
1206 $rerr =
"Got {$size}/{$rhdrs['content-length']} bytes";
1207 $this->
onError(
null, __METHOD__,
1208 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1212 $stat[
'latest'] = $isLatest;
1213 $this->cheapCache->set(
$path,
'stat', $stat );
1214 } elseif ( $rcode === 404 ) {
1215 $tmpFiles[
$path] =
false;
1217 $tmpFiles[
$path] =
null;
1218 $this->
onError(
null, __METHOD__,
1219 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1227 if ( $this->swiftTempUrlKey !=
'' ||
1228 ( $this->rgwS3AccessKey !=
'' && $this->rgwS3SecretKey !=
'' )
1231 if ( $srcRel ===
null ) {
1241 $expires = time() + $ttl;
1243 if ( $this->swiftTempUrlKey !=
'' ) {
1244 $url = $this->
storageUrl( $auth, $srcCont, $srcRel );
1246 $contPath = parse_url( $this->
storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1247 $signature = hash_hmac(
'sha1',
1248 "GET\n{$expires}\n{$contPath}/{$srcRel}",
1249 $this->swiftTempUrlKey
1252 return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1255 $spath =
'/' . rawurlencode( $srcCont ) .
'/' .
1256 str_replace(
'%2F',
'/', rawurlencode( $srcRel ) );
1258 $signature = base64_encode( hash_hmac(
1260 "GET\n\n\n{$expires}\n{$spath}",
1261 $this->rgwS3SecretKey,
1267 return str_replace(
'/swift/v1',
'', $this->
storageUrl( $auth ) . $spath ) .
1270 'Signature' => $signature,
1271 'Expires' => $expires,
1272 'AWSAccessKeyId' => $this->rgwS3AccessKey
1294 if ( !empty(
$params[
'latest'] ) ) {
1295 $hdrs[
'x-newest'] =
'true';
1312 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1313 $statuses[$index] = $this->
newStatus(
'backend-fail-connect', $this->
name );
1320 $httpReqsByStage = [];
1321 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1323 $reqs = $fileOpHandle->httpOp;
1325 foreach ( $reqs
as $stage => &
$req ) {
1326 list( $container, $relPath ) =
$req[
'url'];
1328 $req[
'headers'] = isset(
$req[
'headers'] ) ?
$req[
'headers'] : [];
1330 $httpReqsByStage[$stage][$index] =
$req;
1336 $reqCount = count( $httpReqsByStage );
1337 for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1338 $httpReqs = $this->
http->runMulti( $httpReqsByStage[$stage] );
1339 foreach ( $httpReqs
as $index => $httpReq ) {
1341 $callback = $fileOpHandles[$index]->callback;
1342 call_user_func_array( $callback, [ $httpReq, $statuses[$index] ] );
1345 if ( !$statuses[$index]->isOK() ) {
1346 $stages = count( $fileOpHandles[$index]->httpOp );
1347 for (
$s = ( $stage + 1 );
$s < $stages; ++
$s ) {
1348 unset( $httpReqsByStage[
$s][$index] );
1384 $status->fatal(
'backend-fail-connect', $this->
name );
1389 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1391 'url' => $this->
storageUrl( $auth, $container ),
1393 'x-container-read' => implode(
',',
$readUsers ),
1394 'x-container-write' => implode(
',',
$writeUsers )
1398 if ( $rcode != 204 && $rcode !== 202 ) {
1399 $status->fatal(
'backend-fail-internal', $this->
name );
1400 $this->logger->error( __METHOD__ .
': unexpected rcode value ({rcode})',
1401 [
'rcode' => $rcode ] );
1418 if ( $bypassCache ) {
1419 $this->containerStatCache->clear( $container );
1420 } elseif ( !$this->containerStatCache->has( $container,
'stat' ) ) {
1423 if ( !$this->containerStatCache->has( $container,
'stat' ) ) {
1429 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1431 'url' => $this->
storageUrl( $auth, $container ),
1435 if ( $rcode === 204 ) {
1437 'count' => $rhdrs[
'x-container-object-count'],
1438 'bytes' => $rhdrs[
'x-container-bytes-used']
1440 if ( $bypassCache ) {
1443 $this->containerStatCache->set( $container,
'stat', $stat );
1446 } elseif ( $rcode === 404 ) {
1449 $this->
onError(
null, __METHOD__,
1450 [
'cont' => $container ], $rerr, $rcode, $rdesc );
1456 return $this->containerStatCache->get( $container,
'stat' );
1471 $status->fatal(
'backend-fail-connect', $this->
name );
1477 if ( empty(
$params[
'noAccess'] ) ) {
1479 $readUsers = array_merge( $this->readUsers, [
'.r:*', $this->swiftUser ] );
1480 $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] );
1483 $readUsers = array_merge( $this->secureReadUsers, [ $this->swiftUser ] );
1484 $writeUsers = array_merge( $this->secureWriteUsers, [ $this->swiftUser ] );
1487 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1489 'url' => $this->
storageUrl( $auth, $container ),
1491 'x-container-read' => implode(
',',
$readUsers ),
1492 'x-container-write' => implode(
',',
$writeUsers )
1496 if ( $rcode === 201 ) {
1498 } elseif ( $rcode === 202 ) {
1519 $status->fatal(
'backend-fail-connect', $this->
name );
1524 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1525 'method' =>
'DELETE',
1526 'url' => $this->
storageUrl( $auth, $container ),
1530 if ( $rcode >= 200 && $rcode <= 299 ) {
1531 $this->containerStatCache->clear( $container );
1532 } elseif ( $rcode === 404 ) {
1534 } elseif ( $rcode === 409 ) {
1556 $fullCont,
$type, $limit, $after =
null, $prefix =
null, $delim =
null
1562 $status->fatal(
'backend-fail-connect', $this->
name );
1567 $query = [
'limit' => $limit ];
1568 if (
$type ===
'info' ) {
1569 $query[
'format'] =
'json';
1571 if ( $after !==
null ) {
1572 $query[
'marker'] = $after;
1574 if ( $prefix !==
null ) {
1575 $query[
'prefix'] = $prefix;
1577 if ( $delim !==
null ) {
1578 $query[
'delimiter'] = $delim;
1581 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1583 'url' => $this->
storageUrl( $auth, $fullCont ),
1588 $params = [
'cont' => $fullCont,
'prefix' => $prefix,
'delim' => $delim ];
1589 if ( $rcode === 200 ) {
1590 if (
$type ===
'info' ) {
1593 $status->value = explode(
"\n", trim( $rbody ) );
1595 } elseif ( $rcode === 204 ) {
1597 } elseif ( $rcode === 404 ) {
1607 foreach ( $containerInfo
as $container => $info ) {
1608 $this->containerStatCache->set( $container,
'stat', $info );
1620 if ( $srcRel ===
null ) {
1621 $stats[
$path] =
false;
1623 } elseif ( !$auth ) {
1624 $stats[
$path] =
null;
1630 if ( $cstat ===
false ) {
1631 $stats[
$path] =
false;
1633 } elseif ( !is_array( $cstat ) ) {
1634 $stats[
$path] =
null;
1640 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1645 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1646 $reqs = $this->
http->runMulti( $reqs, $opts );
1649 if ( array_key_exists(
$path, $stats ) ) {
1653 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[
$path][
'response'];
1654 if ( $rcode === 200 || $rcode === 204 ) {
1659 if ( $this->isRGW ) {
1660 $stat[
'latest'] =
true;
1662 } elseif ( $rcode === 404 ) {
1666 $this->
onError(
null, __METHOD__,
$params, $rerr, $rcode, $rdesc );
1668 $stats[
$path] = $stat;
1682 $headers = $this->
sanitizeHdrs( [
'headers' => $rhdrs ] );
1688 'size' => isset( $rhdrs[
'content-length'] ) ? (int)$rhdrs[
'content-length'] : 0,
1689 'sha1' => isset( $metadata[
'sha1base36'] ) ? $metadata[
'sha1base36'] :
null,
1691 'md5' => ctype_xdigit( $rhdrs[
'etag'] ) ? $rhdrs[
'etag'] :
null,
1692 'xattr' => [
'metadata' => $metadata,
'headers' => $headers ]
1700 if ( $this->authErrorTimestamp !==
null ) {
1701 if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
1704 $this->authErrorTimestamp =
null;
1710 if ( !$this->authCreds || $reAuth ) {
1711 $this->authSessionTimestamp = 0;
1713 $creds = $this->srvCache->get( $cacheKey );
1715 if ( isset( $creds[
'auth_token'] ) && isset( $creds[
'storage_url'] ) ) {
1716 $this->authCreds = $creds;
1718 $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
1720 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1722 'url' =>
"{$this->swiftAuthUrl}/v1.0",
1724 'x-auth-user' => $this->swiftUser,
1725 'x-auth-key' => $this->swiftKey
1729 if ( $rcode >= 200 && $rcode <= 299 ) {
1730 $this->authCreds = [
1731 'auth_token' => $rhdrs[
'x-auth-token'],
1732 'storage_url' => ( $this->swiftStorageUrl !== null )
1733 ? $this->swiftStorageUrl
1734 : $rhdrs[
'x-storage-url']
1737 $this->srvCache->set( $cacheKey, $this->authCreds, ceil( $this->authTTL / 2 ) );
1738 $this->authSessionTimestamp = time();
1739 } elseif ( $rcode === 401 ) {
1740 $this->
onError(
null, __METHOD__, [],
"Authentication failed.", $rcode );
1741 $this->authErrorTimestamp = time();
1745 $this->
onError(
null, __METHOD__, [],
"HTTP return code: $rcode", $rcode );
1746 $this->authErrorTimestamp = time();
1752 if ( substr( $this->authCreds[
'storage_url'], -3 ) ===
'/v1' ) {
1753 $this->isRGW =
true;
1767 $parts = [ $creds[
'storage_url'] ];
1768 if ( strlen( $container ) ) {
1769 $parts[] = rawurlencode( $container );
1771 if ( strlen( $object ) ) {
1772 $parts[] = str_replace(
"%2F",
"/", rawurlencode( $object ) );
1775 return implode(
'/', $parts );
1783 return [
'x-auth-token' => $creds[
'auth_token'] ];
1793 return 'swiftcredentials:' . md5(
$username .
':' . $this->swiftAuthUrl );
1809 $status->fatal(
'backend-fail-internal', $this->
name );
1811 if (
$code == 401 ) {
1814 $msg =
"HTTP {code} ({desc}) in '{func}' (given '{params}')";
1823 $msgParams[
'err'] = $err;
1825 $this->logger->error( $msg, $msgParams );
1892 $this->container = $fullCont;
1894 if ( substr( $this->dir, -1 ) ===
'/' ) {
1895 $this->dir = substr( $this->dir, 0, -1 );
1897 if ( $this->dir ==
'' ) {
1898 $this->suffixStart = 0;
1900 $this->suffixStart = strlen( $this->dir ) + 1;
1918 next( $this->bufferIter );
1922 if ( !$this->
valid() && count( $this->bufferIter ) ) {
1924 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1934 $this->bufferAfter =
null;
1936 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1945 if ( $this->bufferIter ===
null ) {
1948 return ( current( $this->bufferIter ) !==
false );
1974 return substr(
current( $this->bufferIter ), $this->suffixStart, -1 );
1992 $relPath = substr(
$path, $this->suffixStart );
1993 if ( is_array( $stat ) ) {
1994 $storageDir = rtrim( $this->params[
'dir'],
'/' );
1995 $this->backend->loadListingStatInternal(
"$storageDir/$relPath", $stat );