101 parent::__construct( $config );
103 $this->swiftAuthUrl = $config[
'swiftAuthUrl'];
104 $this->swiftUser = $config[
'swiftUser'];
105 $this->swiftKey = $config[
'swiftKey'];
107 $this->authTTL = isset( $config[
'swiftAuthTTL'] )
108 ? $config[
'swiftAuthTTL']
110 $this->swiftTempUrlKey = isset( $config[
'swiftTempUrlKey'] )
111 ? $config[
'swiftTempUrlKey']
113 $this->swiftStorageUrl = isset( $config[
'swiftStorageUrl'] )
114 ? $config[
'swiftStorageUrl']
116 $this->shardViaHashLevels = isset( $config[
'shardViaHashLevels'] )
117 ? $config[
'shardViaHashLevels']
119 $this->rgwS3AccessKey = isset( $config[
'rgwS3AccessKey'] )
120 ? $config[
'rgwS3AccessKey']
122 $this->rgwS3SecretKey = isset( $config[
'rgwS3SecretKey'] )
123 ? $config[
'rgwS3SecretKey']
128 if ( isset( $config[
'wanCache'] ) && $config[
'wanCache'] instanceof
WANObjectCache ) {
129 $this->memCache = $config[
'wanCache'];
134 if ( !empty( $config[
'cacheAuthInfo'] ) && isset( $config[
'srvCache'] ) ) {
135 $this->srvCache = $config[
'srvCache'];
147 if ( !mb_check_encoding( $relStoragePath,
'UTF-8' ) ) {
149 } elseif ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
153 return $relStoragePath;
158 if ( $rel ===
null ) {
173 return isset(
$params[
'headers'] )
188 if ( preg_match(
'/^content-(type|length)$/',
$name ) ) {
190 } elseif ( preg_match(
'/^(x-)?content-/',
$name ) ) {
192 } elseif ( preg_match(
'/^content-(disposition)/',
$name ) ) {
197 if ( isset( $headers[
'content-disposition'] ) ) {
200 foreach ( explode(
';', $headers[
'content-disposition'] )
as $part ) {
201 $part = trim( $part );
202 $new = ( $disposition ===
'' ) ? $part :
"{$disposition};{$part}";
203 if ( strlen( $new ) <= 255 ) {
209 $headers[
'content-disposition'] = $disposition;
223 if ( strpos(
$name,
'x-object-meta-' ) === 0 ) {
238 $metadata[substr(
$name, strlen(
'x-object-meta-' ) )] =
$value;
248 if ( $dstRel ===
null ) {
254 $sha1Hash = Wikimedia\base_convert( sha1(
$params[
'content'] ), 16, 36, 31 );
255 $contentType = isset(
$params[
'headers'][
'content-type'] )
256 ?
$params[
'headers'][
'content-type']
261 'url' => [ $dstCont, $dstRel ],
263 'content-length' => strlen(
$params[
'content'] ),
264 'etag' => md5(
$params[
'content'] ),
265 'content-type' => $contentType,
266 'x-object-meta-sha1base36' => $sha1Hash
271 $method = __METHOD__;
273 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
274 if ( $rcode === 201 ) {
276 } elseif ( $rcode === 412 ) {
284 if ( !empty(
$params[
'async'] ) ) {
297 if ( $dstRel ===
null ) {
303 MediaWiki\suppressWarnings();
304 $sha1Hash = sha1_file(
$params[
'src'] );
305 MediaWiki\restoreWarnings();
306 if ( $sha1Hash ===
false ) {
311 $sha1Hash = Wikimedia\base_convert( $sha1Hash, 16, 36, 31 );
312 $contentType = isset(
$params[
'headers'][
'content-type'] )
313 ?
$params[
'headers'][
'content-type']
316 $handle = fopen(
$params[
'src'],
'rb' );
317 if ( $handle ===
false ) {
325 'url' => [ $dstCont, $dstRel ],
327 'content-length' => filesize(
$params[
'src'] ),
328 'etag' => md5_file(
$params[
'src'] ),
329 'content-type' => $contentType,
330 'x-object-meta-sha1base36' => $sha1Hash
335 $method = __METHOD__;
337 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
338 if ( $rcode === 201 ) {
340 } elseif ( $rcode === 412 ) {
348 $opHandle->resourcesToClose[] = $handle;
350 if ( !empty(
$params[
'async'] ) ) {
363 if ( $srcRel ===
null ) {
370 if ( $dstRel ===
null ) {
378 'url' => [ $dstCont, $dstRel ],
380 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
381 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
385 $method = __METHOD__;
387 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
388 if ( $rcode === 201 ) {
390 } elseif ( $rcode === 404 ) {
398 if ( !empty(
$params[
'async'] ) ) {
411 if ( $srcRel ===
null ) {
418 if ( $dstRel ===
null ) {
427 'url' => [ $dstCont, $dstRel ],
429 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
430 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
434 if (
"{$srcCont}/{$srcRel}" !==
"{$dstCont}/{$dstRel}" ) {
436 'method' =>
'DELETE',
437 'url' => [ $srcCont, $srcRel ],
442 $method = __METHOD__;
444 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
445 if (
$request[
'method'] ===
'PUT' && $rcode === 201 ) {
447 } elseif (
$request[
'method'] ===
'DELETE' && $rcode === 204 ) {
449 } elseif ( $rcode === 404 ) {
457 if ( !empty(
$params[
'async'] ) ) {
470 if ( $srcRel ===
null ) {
477 'method' =>
'DELETE',
478 'url' => [ $srcCont, $srcRel ],
482 $method = __METHOD__;
484 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
485 if ( $rcode === 204 ) {
487 } elseif ( $rcode === 404 ) {
488 if ( empty(
$params[
'ignoreMissingSource'] ) ) {
497 if ( !empty(
$params[
'async'] ) ) {
510 if ( $srcRel ===
null ) {
517 $stat = $this->
getFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
518 if ( $stat && !isset( $stat[
'xattr'] ) ) {
519 $stat = $this->
doGetFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
530 $metaHdrs[
"x-object-meta-$name"] =
$value;
532 $customHdrs = $this->
sanitizeHdrs( $params ) + $stat[
'xattr'][
'headers'];
536 'url' => [ $srcCont, $srcRel ],
537 'headers' => $metaHdrs + $customHdrs
540 $method = __METHOD__;
542 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
543 if ( $rcode === 202 ) {
545 } elseif ( $rcode === 404 ) {
553 if ( !empty(
$params[
'async'] ) ) {
567 if ( is_array( $stat ) ) {
569 } elseif ( $stat ===
null ) {
570 $status->fatal(
'backend-fail-internal', $this->
name );
571 $this->logger->error( __METHOD__ .
': cannot get container stat' );
577 if ( $stat ===
false ) {
587 if ( empty(
$params[
'noAccess'] ) ) {
592 if ( is_array( $stat ) ) {
596 [ $this->swiftUser ],
599 } elseif ( $stat ===
false ) {
602 $status->fatal(
'backend-fail-internal', $this->
name );
603 $this->logger->error( __METHOD__ .
': cannot get container stat' );
613 if ( is_array( $stat ) ) {
617 [ $this->swiftUser,
'.r:*' ],
620 } elseif ( $stat ===
false ) {
623 $status->fatal(
'backend-fail-internal', $this->
name );
624 $this->logger->error( __METHOD__ .
': cannot get container stat' );
640 if ( $stat ===
false ) {
642 } elseif ( !is_array( $stat ) ) {
643 $status->fatal(
'backend-fail-internal', $this->
name );
644 $this->logger->error( __METHOD__ .
': cannot get container stat' );
650 if ( $stat[
'count'] == 0 ) {
663 return reset( $stats );
680 return $timestamp->getTimestamp( $format );
681 }
catch ( Exception
$e ) {
694 if ( isset( $objHdrs[
'x-object-meta-sha1base36'] ) ) {
700 $this->logger->error( __METHOD__ .
": $path was not stored with SHA-1 metadata." );
702 $objHdrs[
'x-object-meta-sha1base36'] =
false;
720 $hash = $tmpFile->getSha1Base36();
721 if ( $hash !==
false ) {
722 $objHdrs[
'x-object-meta-sha1base36'] = $hash;
724 $postHeaders[
'x-object-meta-sha1base36'] = $hash;
726 list( $rcode ) = $this->
http->run( [
728 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
731 if ( $rcode >= 200 && $rcode <= 299 ) {
740 $this->logger->error( __METHOD__ .
": unable to set SHA-1 metadata for $path" );
750 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
757 if ( $srcRel ===
null || !$auth ) {
758 $contents[
$path] =
false;
762 $handle = fopen(
'php://temp',
'wb' );
766 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
772 $contents[
$path] =
false;
775 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
776 $reqs = $this->
http->runMulti( $reqs, $opts );
777 foreach ( $reqs
as $path => $op ) {
778 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
779 if ( $rcode >= 200 && $rcode <= 299 ) {
780 rewind( $op[
'stream'] );
781 $contents[
$path] = stream_get_contents( $op[
'stream'] );
782 } elseif ( $rcode === 404 ) {
783 $contents[
$path] =
false;
785 $this->
onError(
null, __METHOD__,
786 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
788 fclose( $op[
'stream'] );
795 $prefix = (
$dir ==
'' ) ?
null :
"{$dir}/";
839 if ( $after === INF ) {
845 $prefix = (
$dir ==
'' ) ?
null :
"{$dir}/";
847 if ( !empty(
$params[
'topOnly'] ) ) {
853 foreach ( $objects
as $object ) {
854 if ( substr( $object, -1 ) ===
'/' ) {
860 $getParentDir =
function (
$path ) {
865 $lastDir = $getParentDir( $after );
874 foreach ( $objects
as $object ) {
875 $objectDir = $getParentDir( $object );
877 if ( $objectDir !==
false && $objectDir !==
$dir ) {
882 if ( strcmp( $objectDir, $lastDir ) > 0 ) {
885 $dirs[] =
"{$pDir}/";
886 $pDir = $getParentDir( $pDir );
887 }
while ( $pDir !==
false
888 && strcmp( $pDir, $lastDir ) > 0
889 && strlen( $pDir ) > strlen(
$dir )
892 $lastDir = $objectDir;
897 if (
count( $objects ) < $limit ) {
900 $after = end( $objects );
919 if ( $after === INF ) {
925 $prefix = (
$dir ==
'' ) ?
null :
"{$dir}/";
928 if ( !empty(
$params[
'topOnly'] ) ) {
929 if ( !empty(
$params[
'adviseStat'] ) ) {
936 if ( !empty(
$params[
'adviseStat'] ) ) {
952 if (
count( $objects ) < $limit ) {
955 $after = end( $objects );
956 $after = is_object( $after ) ? $after->name : $after;
973 foreach ( $objects
as $object ) {
974 if ( is_object( $object ) ) {
975 if ( isset( $object->subdir ) || !isset( $object->name ) ) {
981 'size' => (int)$object->bytes,
984 'md5' => ctype_xdigit( $object->hash ) ? $object->hash :
null,
987 $names[] = [ $object->name, $stat ];
988 } elseif ( substr( $object, -1 ) !==
'/' ) {
990 $names[] = [ $object, null ];
1004 $this->cheapCache->set(
$path,
'stat', $val );
1010 if ( !isset( $stat[
'xattr'] ) ) {
1016 return $stat[
'xattr'];
1025 if ( !isset( $stat[
'sha1'] ) ) {
1031 return $stat[
'sha1'];
1043 if ( $srcRel ===
null ) {
1072 if ( empty(
$params[
'allowOB'] ) ) {
1074 call_user_func( $this->obResetFunc );
1077 $handle = fopen(
'php://output',
'wb' );
1078 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1080 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1083 'stream' => $handle,
1084 'flags' => [
'relayResponseHeaders' => empty(
$params[
'headless'] ) ]
1087 if ( $rcode >= 200 && $rcode <= 299 ) {
1089 } elseif ( $rcode === 404 ) {
1109 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
1116 if ( $srcRel ===
null || !$auth ) {
1117 $tmpFiles[
$path] =
null;
1125 $handle = fopen( $tmpFile->getPath(),
'wb' );
1129 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1132 'stream' => $handle,
1138 $tmpFiles[
$path] = $tmpFile;
1141 $isLatest = ( $this->isRGW || !empty(
$params[
'latest'] ) );
1142 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1143 $reqs = $this->
http->runMulti( $reqs, $opts );
1144 foreach ( $reqs
as $path => $op ) {
1145 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
1146 fclose( $op[
'stream'] );
1147 if ( $rcode >= 200 && $rcode <= 299 ) {
1148 $size = $tmpFiles[
$path] ? $tmpFiles[
$path]->getSize() : 0;
1150 if ( $size != $rhdrs[
'content-length'] ) {
1151 $tmpFiles[
$path] =
null;
1152 $rerr =
"Got {$size}/{$rhdrs['content-length']} bytes";
1153 $this->
onError(
null, __METHOD__,
1154 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1158 $stat[
'latest'] = $isLatest;
1159 $this->cheapCache->set(
$path,
'stat', $stat );
1160 } elseif ( $rcode === 404 ) {
1161 $tmpFiles[
$path] =
false;
1163 $tmpFiles[
$path] =
null;
1164 $this->
onError(
null, __METHOD__,
1165 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1173 if ( $this->swiftTempUrlKey !=
'' ||
1174 ( $this->rgwS3AccessKey !=
'' && $this->rgwS3SecretKey !=
'' )
1177 if ( $srcRel ===
null ) {
1187 $expires = time() + $ttl;
1189 if ( $this->swiftTempUrlKey !=
'' ) {
1190 $url = $this->
storageUrl( $auth, $srcCont, $srcRel );
1192 $contPath = parse_url( $this->
storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1193 $signature = hash_hmac(
'sha1',
1194 "GET\n{$expires}\n{$contPath}/{$srcRel}",
1195 $this->swiftTempUrlKey
1198 return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1201 $spath =
'/' . rawurlencode( $srcCont ) .
'/' .
1202 str_replace(
'%2F',
'/', rawurlencode( $srcRel ) );
1204 $signature = base64_encode( hash_hmac(
1206 "GET\n\n\n{$expires}\n{$spath}",
1207 $this->rgwS3SecretKey,
1213 return str_replace(
'/swift/v1',
'', $this->
storageUrl( $auth ) . $spath ) .
1216 'Signature' => $signature,
1217 'Expires' => $expires,
1218 'AWSAccessKeyId' => $this->rgwS3AccessKey
1240 if ( !empty(
$params[
'latest'] ) ) {
1241 $hdrs[
'x-newest'] =
'true';
1258 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1259 $statuses[$index] = $this->
newStatus(
'backend-fail-connect', $this->
name );
1266 $httpReqsByStage = [];
1267 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1269 $reqs = $fileOpHandle->httpOp;
1271 foreach ( $reqs
as $stage => &
$req ) {
1272 list( $container, $relPath ) =
$req[
'url'];
1274 $req[
'headers'] = isset(
$req[
'headers'] ) ?
$req[
'headers'] : [];
1276 $httpReqsByStage[$stage][$index] =
$req;
1282 $reqCount =
count( $httpReqsByStage );
1283 for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1284 $httpReqs = $this->
http->runMulti( $httpReqsByStage[$stage] );
1285 foreach ( $httpReqs
as $index => $httpReq ) {
1287 $callback = $fileOpHandles[$index]->callback;
1288 call_user_func_array( $callback, [ $httpReq, $statuses[$index] ] );
1291 if ( !$statuses[$index]->isOK() ) {
1292 $stages =
count( $fileOpHandles[$index]->httpOp );
1293 for (
$s = ( $stage + 1 );
$s < $stages; ++
$s ) {
1294 unset( $httpReqsByStage[
$s][$index] );
1330 $status->fatal(
'backend-fail-connect', $this->
name );
1335 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1337 'url' => $this->
storageUrl( $auth, $container ),
1339 'x-container-read' => implode(
',', $readGrps ),
1340 'x-container-write' => implode(
',', $writeGrps )
1344 if ( $rcode != 204 && $rcode !== 202 ) {
1345 $status->fatal(
'backend-fail-internal', $this->
name );
1346 $this->logger->error( __METHOD__ .
': unexpected rcode value (' . $rcode .
')' );
1363 if ( $bypassCache ) {
1364 $this->containerStatCache->clear( $container );
1365 } elseif ( !$this->containerStatCache->has( $container,
'stat' ) ) {
1368 if ( !$this->containerStatCache->has( $container,
'stat' ) ) {
1374 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1376 'url' => $this->
storageUrl( $auth, $container ),
1380 if ( $rcode === 204 ) {
1382 'count' => $rhdrs[
'x-container-object-count'],
1383 'bytes' => $rhdrs[
'x-container-bytes-used']
1385 if ( $bypassCache ) {
1388 $this->containerStatCache->set( $container,
'stat', $stat );
1391 } elseif ( $rcode === 404 ) {
1394 $this->
onError(
null, __METHOD__,
1395 [
'cont' => $container ], $rerr, $rcode, $rdesc );
1401 return $this->containerStatCache->get( $container,
'stat' );
1416 $status->fatal(
'backend-fail-connect', $this->
name );
1422 if ( empty(
$params[
'noAccess'] ) ) {
1429 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1431 'url' => $this->
storageUrl( $auth, $container ),
1433 'x-container-read' => implode(
',', $readGrps ),
1434 'x-container-write' => implode(
',', $writeGrps )
1438 if ( $rcode === 201 ) {
1440 } elseif ( $rcode === 202 ) {
1461 $status->fatal(
'backend-fail-connect', $this->
name );
1466 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1467 'method' =>
'DELETE',
1468 'url' => $this->
storageUrl( $auth, $container ),
1472 if ( $rcode >= 200 && $rcode <= 299 ) {
1473 $this->containerStatCache->clear( $container );
1474 } elseif ( $rcode === 404 ) {
1476 } elseif ( $rcode === 409 ) {
1498 $fullCont,
$type, $limit, $after =
null, $prefix =
null, $delim =
null
1504 $status->fatal(
'backend-fail-connect', $this->
name );
1509 $query = [
'limit' => $limit ];
1510 if (
$type ===
'info' ) {
1511 $query[
'format'] =
'json';
1513 if ( $after !==
null ) {
1514 $query[
'marker'] = $after;
1516 if ( $prefix !==
null ) {
1517 $query[
'prefix'] = $prefix;
1519 if ( $delim !==
null ) {
1520 $query[
'delimiter'] = $delim;
1523 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1525 'url' => $this->
storageUrl( $auth, $fullCont ),
1530 $params = [
'cont' => $fullCont,
'prefix' => $prefix,
'delim' => $delim ];
1531 if ( $rcode === 200 ) {
1532 if (
$type ===
'info' ) {
1535 $status->value = explode(
"\n", trim( $rbody ) );
1537 } elseif ( $rcode === 204 ) {
1539 } elseif ( $rcode === 404 ) {
1549 foreach ( $containerInfo
as $container => $info ) {
1550 $this->containerStatCache->set( $container,
'stat', $info );
1562 if ( $srcRel ===
null ) {
1563 $stats[
$path] =
false;
1565 } elseif ( !$auth ) {
1566 $stats[
$path] =
null;
1572 if ( $cstat ===
false ) {
1573 $stats[
$path] =
false;
1575 } elseif ( !is_array( $cstat ) ) {
1576 $stats[
$path] =
null;
1582 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1587 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1588 $reqs = $this->
http->runMulti( $reqs, $opts );
1591 if ( array_key_exists(
$path, $stats ) ) {
1595 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[
$path][
'response'];
1596 if ( $rcode === 200 || $rcode === 204 ) {
1601 if ( $this->isRGW ) {
1602 $stat[
'latest'] =
true;
1604 } elseif ( $rcode === 404 ) {
1608 $this->
onError(
null, __METHOD__,
$params, $rerr, $rcode, $rdesc );
1610 $stats[
$path] = $stat;
1624 $headers = $this->
sanitizeHdrs( [
'headers' => $rhdrs ] );
1630 'size' => isset( $rhdrs[
'content-length'] ) ? (int)$rhdrs[
'content-length'] : 0,
1631 'sha1' => isset( $metadata[
'sha1base36'] ) ? $metadata[
'sha1base36'] :
null,
1633 'md5' => ctype_xdigit( $rhdrs[
'etag'] ) ? $rhdrs[
'etag'] :
null,
1634 'xattr' => [
'metadata' => $metadata,
'headers' => $headers ]
1642 if ( $this->authErrorTimestamp !==
null ) {
1643 if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
1646 $this->authErrorTimestamp =
null;
1652 if ( !$this->authCreds || $reAuth ) {
1653 $this->authSessionTimestamp = 0;
1655 $creds = $this->srvCache->get( $cacheKey );
1657 if ( isset( $creds[
'auth_token'] ) && isset( $creds[
'storage_url'] ) ) {
1658 $this->authCreds = $creds;
1660 $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
1662 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1664 'url' =>
"{$this->swiftAuthUrl}/v1.0",
1666 'x-auth-user' => $this->swiftUser,
1667 'x-auth-key' => $this->swiftKey
1671 if ( $rcode >= 200 && $rcode <= 299 ) {
1672 $this->authCreds = [
1673 'auth_token' => $rhdrs[
'x-auth-token'],
1674 'storage_url' => ( $this->swiftStorageUrl !== null )
1675 ? $this->swiftStorageUrl
1676 : $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 );