107 parent::__construct( $config );
109 $this->swiftAuthUrl = $config[
'swiftAuthUrl'];
110 $this->swiftUser = $config[
'swiftUser'];
111 $this->swiftKey = $config[
'swiftKey'];
113 $this->authTTL = isset( $config[
'swiftAuthTTL'] )
114 ? $config[
'swiftAuthTTL']
116 $this->swiftTempUrlKey = isset( $config[
'swiftTempUrlKey'] )
117 ? $config[
'swiftTempUrlKey']
119 $this->shardViaHashLevels = isset( $config[
'shardViaHashLevels'] )
120 ? $config[
'shardViaHashLevels']
122 $this->rgwS3AccessKey = isset( $config[
'rgwS3AccessKey'] )
123 ? $config[
'rgwS3AccessKey']
125 $this->rgwS3SecretKey = isset( $config[
'rgwS3SecretKey'] )
126 ? $config[
'rgwS3SecretKey']
131 if ( isset( $config[
'wanCache'] ) && $config[
'wanCache'] instanceof
WANObjectCache ) {
132 $this->memCache = $config[
'wanCache'];
137 if ( !empty( $config[
'cacheAuthInfo'] ) && isset( $config[
'srvCache'] ) ) {
138 $this->srvCache = $config[
'srvCache'];
150 if ( !mb_check_encoding( $relStoragePath,
'UTF-8' ) ) {
152 } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
156 return $relStoragePath;
161 if ( $rel ===
null ) {
176 return isset(
$params[
'headers'] )
191 if ( preg_match(
'/^content-(type|length)$/',
$name ) ) {
193 } elseif ( preg_match(
'/^(x-)?content-/',
$name ) ) {
195 } elseif ( preg_match(
'/^content-(disposition)/',
$name ) ) {
200 if ( isset( $headers[
'content-disposition'] ) ) {
203 foreach ( explode(
';', $headers[
'content-disposition'] )
as $part ) {
204 $part = trim( $part );
205 $new = ( $disposition ===
'' ) ? $part :
"{$disposition};{$part}";
206 if ( strlen( $new ) <= 255 ) {
212 $headers[
'content-disposition'] = $disposition;
226 if ( strpos(
$name,
'x-object-meta-' ) === 0 ) {
241 $metadata[substr(
$name, strlen(
'x-object-meta-' ) )] =
$value;
251 if ( $dstRel ===
null ) {
257 $sha1Hash = Wikimedia\base_convert( sha1(
$params[
'content'] ), 16, 36, 31 );
258 $contentType = isset(
$params[
'headers'][
'content-type'] )
259 ?
$params[
'headers'][
'content-type']
264 'url' => [ $dstCont, $dstRel ],
266 'content-length' => strlen(
$params[
'content'] ),
267 'etag' => md5(
$params[
'content'] ),
268 'content-type' => $contentType,
269 'x-object-meta-sha1base36' => $sha1Hash
274 $method = __METHOD__;
276 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
277 if ( $rcode === 201 ) {
279 } elseif ( $rcode === 412 ) {
287 if ( !empty(
$params[
'async'] ) ) {
300 if ( $dstRel ===
null ) {
306 MediaWiki\suppressWarnings();
307 $sha1Hash = sha1_file(
$params[
'src'] );
308 MediaWiki\restoreWarnings();
309 if ( $sha1Hash ===
false ) {
314 $sha1Hash = Wikimedia\base_convert( $sha1Hash, 16, 36, 31 );
315 $contentType = isset(
$params[
'headers'][
'content-type'] )
316 ?
$params[
'headers'][
'content-type']
319 $handle = fopen(
$params[
'src'],
'rb' );
320 if ( $handle ===
false ) {
328 'url' => [ $dstCont, $dstRel ],
330 'content-length' => filesize(
$params[
'src'] ),
331 'etag' => md5_file(
$params[
'src'] ),
332 'content-type' => $contentType,
333 'x-object-meta-sha1base36' => $sha1Hash
338 $method = __METHOD__;
340 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
341 if ( $rcode === 201 ) {
343 } elseif ( $rcode === 412 ) {
351 $opHandle->resourcesToClose[] = $handle;
353 if ( !empty(
$params[
'async'] ) ) {
366 if ( $srcRel ===
null ) {
373 if ( $dstRel ===
null ) {
381 'url' => [ $dstCont, $dstRel ],
383 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
384 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
388 $method = __METHOD__;
390 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
391 if ( $rcode === 201 ) {
393 } elseif ( $rcode === 404 ) {
401 if ( !empty(
$params[
'async'] ) ) {
414 if ( $srcRel ===
null ) {
421 if ( $dstRel ===
null ) {
430 'url' => [ $dstCont, $dstRel ],
432 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
433 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
437 if (
"{$srcCont}/{$srcRel}" !==
"{$dstCont}/{$dstRel}" ) {
439 'method' =>
'DELETE',
440 'url' => [ $srcCont, $srcRel ],
445 $method = __METHOD__;
447 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
448 if (
$request[
'method'] ===
'PUT' && $rcode === 201 ) {
450 } elseif (
$request[
'method'] ===
'DELETE' && $rcode === 204 ) {
452 } elseif ( $rcode === 404 ) {
460 if ( !empty(
$params[
'async'] ) ) {
473 if ( $srcRel ===
null ) {
480 'method' =>
'DELETE',
481 'url' => [ $srcCont, $srcRel ],
485 $method = __METHOD__;
487 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
488 if ( $rcode === 204 ) {
490 } elseif ( $rcode === 404 ) {
491 if ( empty(
$params[
'ignoreMissingSource'] ) ) {
500 if ( !empty(
$params[
'async'] ) ) {
513 if ( $srcRel ===
null ) {
520 $stat = $this->
getFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
521 if ( $stat && !isset( $stat[
'xattr'] ) ) {
522 $stat = $this->
doGetFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
533 $metaHdrs[
"x-object-meta-$name"] =
$value;
535 $customHdrs = $this->
sanitizeHdrs( $params ) + $stat[
'xattr'][
'headers'];
539 'url' => [ $srcCont, $srcRel ],
540 'headers' => $metaHdrs + $customHdrs
543 $method = __METHOD__;
545 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
546 if ( $rcode === 202 ) {
548 } elseif ( $rcode === 404 ) {
556 if ( !empty(
$params[
'async'] ) ) {
570 if ( is_array( $stat ) ) {
572 } elseif ( $stat ===
null ) {
573 $status->fatal(
'backend-fail-internal', $this->
name );
574 $this->logger->error( __METHOD__ .
': cannot get container stat' );
580 if ( $stat ===
false ) {
590 if ( empty(
$params[
'noAccess'] ) ) {
595 if ( is_array( $stat ) ) {
599 [ $this->swiftUser ],
602 } elseif ( $stat ===
false ) {
605 $status->fatal(
'backend-fail-internal', $this->
name );
606 $this->logger->error( __METHOD__ .
': cannot get container stat' );
616 if ( is_array( $stat ) ) {
620 [ $this->swiftUser,
'.r:*' ],
623 } elseif ( $stat ===
false ) {
626 $status->fatal(
'backend-fail-internal', $this->
name );
627 $this->logger->error( __METHOD__ .
': cannot get container stat' );
643 if ( $stat ===
false ) {
645 } elseif ( !is_array( $stat ) ) {
646 $status->fatal(
'backend-fail-internal', $this->
name );
647 $this->logger->error( __METHOD__ .
': cannot get container stat' );
653 if ( $stat[
'count'] == 0 ) {
666 return reset( $stats );
683 return $timestamp->getTimestamp( $format );
684 }
catch ( Exception
$e ) {
697 if ( isset( $objHdrs[
'x-object-meta-sha1base36'] ) ) {
703 $this->logger->error( __METHOD__ .
": $path was not stored with SHA-1 metadata." );
705 $objHdrs[
'x-object-meta-sha1base36'] =
false;
723 $hash = $tmpFile->getSha1Base36();
724 if ( $hash !==
false ) {
725 $objHdrs[
'x-object-meta-sha1base36'] = $hash;
727 $postHeaders[
'x-object-meta-sha1base36'] = $hash;
729 list( $rcode ) = $this->
http->run( [
731 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
734 if ( $rcode >= 200 && $rcode <= 299 ) {
743 $this->logger->error( __METHOD__ .
": unable to set SHA-1 metadata for $path" );
753 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
760 if ( $srcRel ===
null || !$auth ) {
761 $contents[
$path] =
false;
765 $handle = fopen(
'php://temp',
'wb' );
769 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
775 $contents[
$path] =
false;
778 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
779 $reqs = $this->
http->runMulti( $reqs, $opts );
780 foreach ( $reqs
as $path => $op ) {
781 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
782 if ( $rcode >= 200 && $rcode <= 299 ) {
783 rewind( $op[
'stream'] );
784 $contents[
$path] = stream_get_contents( $op[
'stream'] );
785 } elseif ( $rcode === 404 ) {
786 $contents[
$path] =
false;
788 $this->
onError(
null, __METHOD__,
789 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
791 fclose( $op[
'stream'] );
798 $prefix = (
$dir ==
'' ) ?
null :
"{$dir}/";
842 if ( $after === INF ) {
848 $prefix = (
$dir ==
'' ) ?
null :
"{$dir}/";
850 if ( !empty(
$params[
'topOnly'] ) ) {
856 foreach ( $objects
as $object ) {
857 if ( substr( $object, -1 ) ===
'/' ) {
863 $getParentDir =
function (
$path ) {
868 $lastDir = $getParentDir( $after );
877 foreach ( $objects
as $object ) {
878 $objectDir = $getParentDir( $object );
880 if ( $objectDir !==
false && $objectDir !==
$dir ) {
885 if ( strcmp( $objectDir, $lastDir ) > 0 ) {
888 $dirs[] =
"{$pDir}/";
889 $pDir = $getParentDir( $pDir );
890 }
while ( $pDir !==
false
891 && strcmp( $pDir, $lastDir ) > 0
892 && strlen( $pDir ) > strlen(
$dir )
895 $lastDir = $objectDir;
903 $after = end( $objects );
922 if ( $after === INF ) {
928 $prefix = (
$dir ==
'' ) ?
null :
"{$dir}/";
931 if ( !empty(
$params[
'topOnly'] ) ) {
932 if ( !empty(
$params[
'adviseStat'] ) ) {
939 if ( !empty(
$params[
'adviseStat'] ) ) {
958 $after = end( $objects );
959 $after = is_object( $after ) ? $after->name : $after;
976 foreach ( $objects
as $object ) {
977 if ( is_object( $object ) ) {
978 if ( isset( $object->subdir ) || !isset( $object->name ) ) {
984 'size' => (int)$object->bytes,
987 'md5' => ctype_xdigit( $object->hash ) ? $object->hash :
null,
990 $names[] = [ $object->name, $stat ];
991 } elseif ( substr( $object, -1 ) !==
'/' ) {
993 $names[] = [ $object, null ];
1007 $this->cheapCache->set(
$path,
'stat', $val );
1013 if ( !isset( $stat[
'xattr'] ) ) {
1019 return $stat[
'xattr'];
1028 if ( !isset( $stat[
'sha1'] ) ) {
1034 return $stat[
'sha1'];
1046 if ( $srcRel ===
null ) {
1075 if ( empty(
$params[
'allowOB'] ) ) {
1077 call_user_func( $this->obResetFunc );
1080 $handle = fopen(
'php://output',
'wb' );
1081 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1083 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1086 'stream' => $handle,
1087 'flags' => [
'relayResponseHeaders' => empty(
$params[
'headless'] ) ]
1090 if ( $rcode >= 200 && $rcode <= 299 ) {
1092 } elseif ( $rcode === 404 ) {
1112 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
1119 if ( $srcRel ===
null || !$auth ) {
1120 $tmpFiles[
$path] =
null;
1128 $handle = fopen( $tmpFile->getPath(),
'wb' );
1132 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1135 'stream' => $handle,
1141 $tmpFiles[
$path] = $tmpFile;
1144 $isLatest = ( $this->isRGW || !empty(
$params[
'latest'] ) );
1145 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1146 $reqs = $this->
http->runMulti( $reqs, $opts );
1147 foreach ( $reqs
as $path => $op ) {
1148 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
1149 fclose( $op[
'stream'] );
1150 if ( $rcode >= 200 && $rcode <= 299 ) {
1151 $size = $tmpFiles[
$path] ? $tmpFiles[
$path]->getSize() : 0;
1153 if ( $size != $rhdrs[
'content-length'] ) {
1154 $tmpFiles[
$path] =
null;
1155 $rerr =
"Got {$size}/{$rhdrs['content-length']} bytes";
1156 $this->
onError(
null, __METHOD__,
1157 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1161 $stat[
'latest'] = $isLatest;
1162 $this->cheapCache->set(
$path,
'stat', $stat );
1163 } elseif ( $rcode === 404 ) {
1164 $tmpFiles[
$path] =
false;
1166 $tmpFiles[
$path] =
null;
1167 $this->
onError(
null, __METHOD__,
1168 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1176 if ( $this->swiftTempUrlKey !=
'' ||
1177 ( $this->rgwS3AccessKey !=
'' && $this->rgwS3SecretKey !=
'' )
1180 if ( $srcRel ===
null ) {
1190 $expires = time() + $ttl;
1192 if ( $this->swiftTempUrlKey !=
'' ) {
1193 $url = $this->
storageUrl( $auth, $srcCont, $srcRel );
1195 $contPath = parse_url( $this->
storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1196 $signature = hash_hmac(
'sha1',
1197 "GET\n{$expires}\n{$contPath}/{$srcRel}",
1198 $this->swiftTempUrlKey
1201 return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1204 $spath =
'/' . rawurlencode( $srcCont ) .
'/' .
1205 str_replace(
'%2F',
'/', rawurlencode( $srcRel ) );
1207 $signature = base64_encode( hash_hmac(
1209 "GET\n\n\n{$expires}\n{$spath}",
1210 $this->rgwS3SecretKey,
1216 return str_replace(
'/swift/v1',
'', $this->
storageUrl( $auth ) . $spath ) .
1219 'Signature' => $signature,
1220 'Expires' => $expires,
1221 'AWSAccessKeyId' => $this->rgwS3AccessKey
1243 if ( !empty(
$params[
'latest'] ) ) {
1244 $hdrs[
'x-newest'] =
'true';
1261 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1262 $statuses[$index] = $this->
newStatus(
'backend-fail-connect', $this->
name );
1269 $httpReqsByStage = [];
1270 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1272 $reqs = $fileOpHandle->httpOp;
1274 foreach ( $reqs
as $stage => &
$req ) {
1275 list( $container, $relPath ) =
$req[
'url'];
1277 $req[
'headers'] = isset(
$req[
'headers'] ) ?
$req[
'headers'] : [];
1279 $httpReqsByStage[$stage][$index] =
$req;
1285 $reqCount =
count( $httpReqsByStage );
1286 for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1287 $httpReqs = $this->
http->runMulti( $httpReqsByStage[$stage] );
1288 foreach ( $httpReqs
as $index => $httpReq ) {
1290 $callback = $fileOpHandles[$index]->callback;
1291 call_user_func_array( $callback, [ $httpReq, $statuses[$index] ] );
1294 if ( !$statuses[$index]->isOK() ) {
1295 $stages =
count( $fileOpHandles[$index]->httpOp );
1296 for (
$s = ( $stage + 1 );
$s < $stages; ++
$s ) {
1297 unset( $httpReqsByStage[
$s][$index] );
1333 $status->fatal(
'backend-fail-connect', $this->
name );
1338 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1340 'url' => $this->
storageUrl( $auth, $container ),
1342 'x-container-read' => implode(
',', $readGrps ),
1343 'x-container-write' => implode(
',', $writeGrps )
1347 if ( $rcode != 204 && $rcode !== 202 ) {
1348 $status->fatal(
'backend-fail-internal', $this->
name );
1349 $this->logger->error( __METHOD__ .
': unexpected rcode value (' . $rcode .
')' );
1366 if ( $bypassCache ) {
1367 $this->containerStatCache->clear( $container );
1368 } elseif ( !$this->containerStatCache->has( $container,
'stat' ) ) {
1371 if ( !$this->containerStatCache->has( $container,
'stat' ) ) {
1377 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1379 'url' => $this->
storageUrl( $auth, $container ),
1383 if ( $rcode === 204 ) {
1385 'count' => $rhdrs[
'x-container-object-count'],
1386 'bytes' => $rhdrs[
'x-container-bytes-used']
1388 if ( $bypassCache ) {
1391 $this->containerStatCache->set( $container,
'stat', $stat );
1394 } elseif ( $rcode === 404 ) {
1397 $this->
onError(
null, __METHOD__,
1398 [
'cont' => $container ], $rerr, $rcode, $rdesc );
1404 return $this->containerStatCache->get( $container,
'stat' );
1419 $status->fatal(
'backend-fail-connect', $this->
name );
1425 if ( empty(
$params[
'noAccess'] ) ) {
1432 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1434 'url' => $this->
storageUrl( $auth, $container ),
1436 'x-container-read' => implode(
',', $readGrps ),
1437 'x-container-write' => implode(
',', $writeGrps )
1441 if ( $rcode === 201 ) {
1443 } elseif ( $rcode === 202 ) {
1464 $status->fatal(
'backend-fail-connect', $this->
name );
1469 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1470 'method' =>
'DELETE',
1471 'url' => $this->
storageUrl( $auth, $container ),
1475 if ( $rcode >= 200 && $rcode <= 299 ) {
1476 $this->containerStatCache->clear( $container );
1477 } elseif ( $rcode === 404 ) {
1479 } elseif ( $rcode === 409 ) {
1501 $fullCont,
$type,
$limit, $after =
null, $prefix =
null, $delim =
null
1507 $status->fatal(
'backend-fail-connect', $this->
name );
1513 if (
$type ===
'info' ) {
1514 $query[
'format'] =
'json';
1516 if ( $after !==
null ) {
1517 $query[
'marker'] = $after;
1519 if ( $prefix !==
null ) {
1520 $query[
'prefix'] = $prefix;
1522 if ( $delim !==
null ) {
1523 $query[
'delimiter'] = $delim;
1526 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1528 'url' => $this->
storageUrl( $auth, $fullCont ),
1533 $params = [
'cont' => $fullCont,
'prefix' => $prefix,
'delim' => $delim ];
1534 if ( $rcode === 200 ) {
1535 if (
$type ===
'info' ) {
1538 $status->value = explode(
"\n", trim( $rbody ) );
1540 } elseif ( $rcode === 204 ) {
1542 } elseif ( $rcode === 404 ) {
1552 foreach ( $containerInfo
as $container => $info ) {
1553 $this->containerStatCache->set( $container,
'stat', $info );
1565 if ( $srcRel ===
null ) {
1566 $stats[
$path] =
false;
1568 } elseif ( !$auth ) {
1569 $stats[
$path] =
null;
1575 if ( $cstat ===
false ) {
1576 $stats[
$path] =
false;
1578 } elseif ( !is_array( $cstat ) ) {
1579 $stats[
$path] =
null;
1585 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1590 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1591 $reqs = $this->
http->runMulti( $reqs, $opts );
1594 if ( array_key_exists(
$path, $stats ) ) {
1598 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[
$path][
'response'];
1599 if ( $rcode === 200 || $rcode === 204 ) {
1604 if ( $this->isRGW ) {
1605 $stat[
'latest'] =
true;
1607 } elseif ( $rcode === 404 ) {
1611 $this->
onError(
null, __METHOD__,
$params, $rerr, $rcode, $rdesc );
1613 $stats[
$path] = $stat;
1627 $headers = $this->
sanitizeHdrs( [
'headers' => $rhdrs ] );
1633 'size' => isset( $rhdrs[
'content-length'] ) ? (int)$rhdrs[
'content-length'] : 0,
1634 'sha1' => isset( $metadata[
'sha1base36'] ) ? $metadata[
'sha1base36'] :
null,
1636 'md5' => ctype_xdigit( $rhdrs[
'etag'] ) ? $rhdrs[
'etag'] :
null,
1637 'xattr' => [
'metadata' => $metadata,
'headers' => $headers ]
1645 if ( $this->authErrorTimestamp !==
null ) {
1646 if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
1649 $this->authErrorTimestamp =
null;
1655 if ( !$this->authCreds || $reAuth ) {
1656 $this->authSessionTimestamp = 0;
1658 $creds = $this->srvCache->get( $cacheKey );
1660 if ( isset( $creds[
'auth_token'] ) && isset( $creds[
'storage_url'] ) ) {
1661 $this->authCreds = $creds;
1663 $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
1665 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1667 'url' =>
"{$this->swiftAuthUrl}/v1.0",
1669 'x-auth-user' => $this->swiftUser,
1670 'x-auth-key' => $this->swiftKey
1674 if ( $rcode >= 200 && $rcode <= 299 ) {
1675 $this->authCreds = [
1676 'auth_token' => $rhdrs[
'x-auth-token'],
1677 'storage_url' => $rhdrs[
'x-storage-url']
1679 $this->srvCache->set( $cacheKey, $this->authCreds, ceil( $this->authTTL / 2 ) );
1680 $this->authSessionTimestamp = time();
1681 } elseif ( $rcode === 401 ) {
1682 $this->
onError(
null, __METHOD__, [],
"Authentication failed.", $rcode );
1683 $this->authErrorTimestamp = time();
1687 $this->
onError(
null, __METHOD__, [],
"HTTP return code: $rcode", $rcode );
1688 $this->authErrorTimestamp = time();
1694 if ( substr( $this->authCreds[
'storage_url'], -3 ) ===
'/v1' ) {
1695 $this->isRGW =
true;
1709 $parts = [ $creds[
'storage_url'] ];
1710 if ( strlen( $container ) ) {
1711 $parts[] = rawurlencode( $container );
1713 if ( strlen( $object ) ) {
1714 $parts[] = str_replace(
"%2F",
"/", rawurlencode( $object ) );
1717 return implode(
'/', $parts );
1725 return [
'x-auth-token' => $creds[
'auth_token'] ];
1735 return 'swiftcredentials:' . md5(
$username .
':' . $this->swiftAuthUrl );
1751 $status->fatal(
'backend-fail-internal', $this->
name );
1753 if (
$code == 401 ) {
1756 $this->logger->error(
1758 ( $err ?
": $err" :
"" )
1826 $this->container = $fullCont;
1828 if ( substr( $this->dir, -1 ) ===
'/' ) {
1829 $this->dir = substr( $this->dir, 0, -1 );
1831 if ( $this->dir ==
'' ) {
1832 $this->suffixStart = 0;
1834 $this->suffixStart = strlen( $this->dir ) + 1;
1852 next( $this->bufferIter );
1856 if ( !$this->
valid() &&
count( $this->bufferIter ) ) {
1858 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1868 $this->bufferAfter =
null;
1870 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1879 if ( $this->bufferIter ===
null ) {
1882 return ( current( $this->bufferIter ) !==
false );
1908 return substr(
current( $this->bufferIter ), $this->suffixStart, -1 );
1926 $relPath = substr(
$path, $this->suffixStart );
1927 if ( is_array( $stat ) ) {
1928 $storageDir = rtrim( $this->params[
'dir'],
'/' );
1929 $this->backend->loadListingStatInternal(
"$storageDir/$relPath", $stat );