113 parent::__construct( $config );
115 $this->swiftAuthUrl = $config[
'swiftAuthUrl'];
116 $this->swiftUser = $config[
'swiftUser'];
117 $this->swiftKey = $config[
'swiftKey'];
119 $this->authTTL = $config[
'swiftAuthTTL'] ?? 15 * 60;
120 $this->swiftTempUrlKey = $config[
'swiftTempUrlKey'] ??
'';
121 $this->swiftStorageUrl = $config[
'swiftStorageUrl'] ??
null;
122 $this->shardViaHashLevels = $config[
'shardViaHashLevels'] ??
'';
123 $this->rgwS3AccessKey = $config[
'rgwS3AccessKey'] ??
'';
124 $this->rgwS3SecretKey = $config[
'rgwS3SecretKey'] ??
'';
128 if ( isset( $config[
'wanCache'] ) && $config[
'wanCache'] instanceof
WANObjectCache ) {
129 $this->memCache = $config[
'wanCache'];
132 $this->containerStatCache =
new MapCacheLRU( 300 );
134 if ( !empty( $config[
'cacheAuthInfo'] ) && isset( $config[
'srvCache'] ) ) {
135 $this->srvCache = $config[
'srvCache'];
139 $this->readUsers = $config[
'readUsers'] ?? [];
140 $this->writeUsers = $config[
'writeUsers'] ?? [];
141 $this->secureReadUsers = $config[
'secureReadUsers'] ?? [];
142 $this->secureWriteUsers = $config[
'secureWriteUsers'] ?? [];
151 if ( !mb_check_encoding( $relStoragePath,
'UTF-8' ) ) {
153 } elseif ( strlen( rawurlencode( $relStoragePath ) ) > 1024 ) {
157 return $relStoragePath;
162 if ( $rel ===
null ) {
177 if ( !isset(
$params[
'headers'] ) ) {
182 unset( $headers[
'content-type' ] );
200 return isset(
$params[
'headers'] )
215 if ( preg_match(
'/^content-length$/',
$name ) ) {
217 } elseif ( preg_match(
'/^(x-)?content-/',
$name ) ) {
219 } elseif ( preg_match(
'/^content-(disposition)/',
$name ) ) {
224 if ( isset( $headers[
'content-disposition'] ) ) {
227 foreach ( explode(
';', $headers[
'content-disposition'] )
as $part ) {
228 $part = trim( $part );
229 $new = ( $disposition ===
'' ) ? $part :
"{$disposition};{$part}";
230 if ( strlen( $new ) <= 255 ) {
236 $headers[
'content-disposition'] = $disposition;
250 if ( strpos(
$name,
'x-object-meta-' ) === 0 ) {
265 $metadata[substr(
$name, strlen(
'x-object-meta-' ) )] =
$value;
275 if ( $dstRel ===
null ) {
281 $sha1Hash = Wikimedia\base_convert( sha1(
$params[
'content'] ), 16, 36, 31 );
282 $contentType =
$params[
'headers'][
'content-type']
287 'url' => [ $dstCont, $dstRel ],
289 'content-length' => strlen(
$params[
'content'] ),
290 'etag' => md5(
$params[
'content'] ),
291 'content-type' => $contentType,
292 'x-object-meta-sha1base36' => $sha1Hash
297 $method = __METHOD__;
299 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
300 if ( $rcode === 201 ) {
302 } elseif ( $rcode === 412 ) {
310 if ( !empty(
$params[
'async'] ) ) {
323 if ( $dstRel ===
null ) {
329 Wikimedia\suppressWarnings();
330 $sha1Hash = sha1_file(
$params[
'src'] );
331 Wikimedia\restoreWarnings();
332 if ( $sha1Hash ===
false ) {
337 $sha1Hash = Wikimedia\base_convert( $sha1Hash, 16, 36, 31 );
338 $contentType =
$params[
'headers'][
'content-type']
341 $handle = fopen(
$params[
'src'],
'rb' );
342 if ( $handle ===
false ) {
350 'url' => [ $dstCont, $dstRel ],
352 'content-length' => filesize(
$params[
'src'] ),
353 'etag' => md5_file(
$params[
'src'] ),
354 'content-type' => $contentType,
355 'x-object-meta-sha1base36' => $sha1Hash
360 $method = __METHOD__;
362 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
363 if ( $rcode === 201 ) {
365 } elseif ( $rcode === 412 ) {
373 $opHandle->resourcesToClose[] = $handle;
375 if ( !empty(
$params[
'async'] ) ) {
388 if ( $srcRel ===
null ) {
395 if ( $dstRel ===
null ) {
403 'url' => [ $dstCont, $dstRel ],
405 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
406 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
410 $method = __METHOD__;
412 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
413 if ( $rcode === 201 ) {
415 } elseif ( $rcode === 404 ) {
423 if ( !empty(
$params[
'async'] ) ) {
436 if ( $srcRel ===
null ) {
443 if ( $dstRel ===
null ) {
452 'url' => [ $dstCont, $dstRel ],
454 'x-copy-from' =>
'/' . rawurlencode( $srcCont ) .
455 '/' . str_replace(
"%2F",
"/", rawurlencode( $srcRel ) )
459 if (
"{$srcCont}/{$srcRel}" !==
"{$dstCont}/{$dstRel}" ) {
461 'method' =>
'DELETE',
462 'url' => [ $srcCont, $srcRel ],
467 $method = __METHOD__;
469 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
470 if (
$request[
'method'] ===
'PUT' && $rcode === 201 ) {
472 } elseif (
$request[
'method'] ===
'DELETE' && $rcode === 204 ) {
474 } elseif ( $rcode === 404 ) {
482 if ( !empty(
$params[
'async'] ) ) {
495 if ( $srcRel ===
null ) {
502 'method' =>
'DELETE',
503 'url' => [ $srcCont, $srcRel ],
507 $method = __METHOD__;
509 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
510 if ( $rcode === 204 ) {
512 } elseif ( $rcode === 404 ) {
513 if ( empty(
$params[
'ignoreMissingSource'] ) ) {
522 if ( !empty(
$params[
'async'] ) ) {
535 if ( $srcRel ===
null ) {
542 $stat = $this->
getFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
543 if ( $stat && !isset( $stat[
'xattr'] ) ) {
544 $stat = $this->
doGetFileStat( [
'src' => $params[
'src'],
'latest' => 1 ] );
555 $metaHdrs[
"x-object-meta-$name"] =
$value;
557 $customHdrs = $this->
sanitizeHdrs( $params ) + $stat[
'xattr'][
'headers'];
561 'url' => [ $srcCont, $srcRel ],
562 'headers' => $metaHdrs + $customHdrs
565 $method = __METHOD__;
567 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) =
$request[
'response'];
568 if ( $rcode === 202 ) {
570 } elseif ( $rcode === 404 ) {
578 if ( !empty(
$params[
'async'] ) ) {
592 if ( is_array( $stat ) ) {
594 } elseif ( $stat ===
null ) {
595 $status->fatal(
'backend-fail-internal', $this->
name );
596 $this->logger->error( __METHOD__ .
': cannot get container stat' );
602 if ( $stat ===
false ) {
612 if ( empty(
$params[
'noAccess'] ) ) {
617 if ( is_array( $stat ) ) {
618 $readUsers = array_merge( $this->secureReadUsers, [ $this->swiftUser ] );
619 $writeUsers = array_merge( $this->secureWriteUsers, [ $this->swiftUser ] );
626 } elseif ( $stat ===
false ) {
629 $status->fatal(
'backend-fail-internal', $this->
name );
630 $this->logger->error( __METHOD__ .
': cannot get container stat' );
640 if ( is_array( $stat ) ) {
641 $readUsers = array_merge( $this->readUsers, [ $this->swiftUser,
'.r:*' ] );
642 $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] );
650 } elseif ( $stat ===
false ) {
653 $status->fatal(
'backend-fail-internal', $this->
name );
654 $this->logger->error( __METHOD__ .
': cannot get container stat' );
670 if ( $stat ===
false ) {
672 } elseif ( !is_array( $stat ) ) {
673 $status->fatal(
'backend-fail-internal', $this->
name );
674 $this->logger->error( __METHOD__ .
': cannot get container stat' );
680 if ( $stat[
'count'] == 0 ) {
693 return reset( $stats );
710 return $timestamp->getTimestamp( $format );
711 }
catch ( Exception
$e ) {
724 if ( isset( $objHdrs[
'x-object-meta-sha1base36'] ) ) {
730 $this->logger->error( __METHOD__ .
": {path} was not stored with SHA-1 metadata.",
731 [
'path' =>
$path ] );
733 $objHdrs[
'x-object-meta-sha1base36'] =
false;
751 $hash = $tmpFile->getSha1Base36();
752 if ( $hash !==
false ) {
753 $objHdrs[
'x-object-meta-sha1base36'] = $hash;
755 $postHeaders[
'x-object-meta-sha1base36'] = $hash;
757 list( $rcode ) = $this->
http->run( [
759 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
762 if ( $rcode >= 200 && $rcode <= 299 ) {
771 $this->logger->error( __METHOD__ .
': unable to set SHA-1 metadata for {path}',
772 [
'path' =>
$path ] );
782 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
789 if ( $srcRel ===
null || !$auth ) {
790 $contents[
$path] =
false;
794 $handle = fopen(
'php://temp',
'wb' );
798 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
804 $contents[
$path] =
false;
807 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
808 $reqs = $this->
http->runMulti( $reqs, $opts );
809 foreach ( $reqs
as $path => $op ) {
810 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
811 if ( $rcode >= 200 && $rcode <= 299 ) {
812 rewind( $op[
'stream'] );
813 $contents[
$path] = stream_get_contents( $op[
'stream'] );
814 } elseif ( $rcode === 404 ) {
815 $contents[
$path] =
false;
817 $this->
onError(
null, __METHOD__,
818 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
820 fclose( $op[
'stream'] );
827 $prefix = ( $dir ==
'' ) ?
null :
"{$dir}/";
871 if ( $after === INF ) {
877 $prefix = ( $dir ==
'' ) ?
null :
"{$dir}/";
879 if ( !empty(
$params[
'topOnly'] ) ) {
885 foreach ( $objects
as $object ) {
886 if ( substr( $object, -1 ) ===
'/' ) {
892 $getParentDir =
function (
$path ) {
897 $lastDir = $getParentDir( $after );
906 foreach ( $objects
as $object ) {
907 $objectDir = $getParentDir( $object );
909 if ( $objectDir !==
false && $objectDir !== $dir ) {
914 if ( strcmp( $objectDir, $lastDir ) > 0 ) {
917 $dirs[] =
"{$pDir}/";
918 $pDir = $getParentDir( $pDir );
919 }
while ( $pDir !==
false
920 && strcmp( $pDir, $lastDir ) > 0
921 && strlen( $pDir ) > strlen( $dir )
924 $lastDir = $objectDir;
929 if (
count( $objects ) < $limit ) {
932 $after = end( $objects );
951 if ( $after === INF ) {
957 $prefix = ( $dir ==
'' ) ?
null :
"{$dir}/";
960 if ( !empty(
$params[
'topOnly'] ) ) {
961 if ( !empty(
$params[
'adviseStat'] ) ) {
968 if ( !empty(
$params[
'adviseStat'] ) ) {
984 if (
count( $objects ) < $limit ) {
987 $after = end( $objects );
988 $after = is_object( $after ) ? $after->name : $after;
1005 foreach ( $objects
as $object ) {
1006 if ( is_object( $object ) ) {
1007 if ( isset( $object->subdir ) || !isset( $object->name ) ) {
1013 'size' => (int)$object->bytes,
1016 'md5' => ctype_xdigit( $object->hash ) ? $object->hash :
null,
1019 $names[] = [ $object->name, $stat ];
1020 } elseif ( substr( $object, -1 ) !==
'/' ) {
1022 $names[] = [ $object,
null ];
1036 $this->cheapCache->setField(
$path,
'stat', $val );
1042 if ( !isset( $stat[
'xattr'] ) ) {
1048 return $stat[
'xattr'];
1057 $params[
'requireSHA1'] =
true;
1061 return $stat[
'sha1'];
1073 if ( $srcRel ===
null ) {
1102 if ( empty(
$params[
'allowOB'] ) ) {
1107 $handle = fopen(
'php://output',
'wb' );
1108 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1110 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1113 'stream' => $handle,
1114 'flags' => [
'relayResponseHeaders' => empty(
$params[
'headless'] ) ]
1117 if ( $rcode >= 200 && $rcode <= 299 ) {
1119 } elseif ( $rcode === 404 ) {
1139 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
1146 if ( $srcRel ===
null || !$auth ) {
1147 $tmpFiles[
$path] =
null;
1155 $handle = fopen( $tmpFile->getPath(),
'wb' );
1159 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1162 'stream' => $handle,
1168 $tmpFiles[
$path] = $tmpFile;
1171 $isLatest = ( $this->isRGW || !empty(
$params[
'latest'] ) );
1172 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1173 $reqs = $this->
http->runMulti( $reqs, $opts );
1174 foreach ( $reqs
as $path => $op ) {
1175 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
1176 fclose( $op[
'stream'] );
1177 if ( $rcode >= 200 && $rcode <= 299 ) {
1178 $size = $tmpFiles[
$path] ? $tmpFiles[
$path]->getSize() : 0;
1180 if ( $size != $rhdrs[
'content-length'] ) {
1181 $tmpFiles[
$path] =
null;
1182 $rerr =
"Got {$size}/{$rhdrs['content-length']} bytes";
1183 $this->
onError(
null, __METHOD__,
1184 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1188 $stat[
'latest'] = $isLatest;
1189 $this->cheapCache->setField(
$path,
'stat', $stat );
1190 } elseif ( $rcode === 404 ) {
1191 $tmpFiles[
$path] =
false;
1193 $tmpFiles[
$path] =
null;
1194 $this->
onError(
null, __METHOD__,
1195 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1203 if ( $this->swiftTempUrlKey !=
'' ||
1204 ( $this->rgwS3AccessKey !=
'' && $this->rgwS3SecretKey !=
'' )
1207 if ( $srcRel ===
null ) {
1216 $ttl =
$params[
'ttl'] ?? 86400;
1217 $expires = time() + $ttl;
1219 if ( $this->swiftTempUrlKey !=
'' ) {
1220 $url = $this->
storageUrl( $auth, $srcCont, $srcRel );
1222 $contPath = parse_url( $this->
storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1223 $signature = hash_hmac(
'sha1',
1224 "GET\n{$expires}\n{$contPath}/{$srcRel}",
1225 $this->swiftTempUrlKey
1228 return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1231 $spath =
'/' . rawurlencode( $srcCont ) .
'/' .
1232 str_replace(
'%2F',
'/', rawurlencode( $srcRel ) );
1234 $signature = base64_encode( hash_hmac(
1236 "GET\n\n\n{$expires}\n{$spath}",
1237 $this->rgwS3SecretKey,
1243 return str_replace(
'/swift/v1',
'', $this->
storageUrl( $auth ) . $spath ) .
1246 'Signature' => $signature,
1247 'Expires' => $expires,
1248 'AWSAccessKeyId' => $this->rgwS3AccessKey
1270 if ( !empty(
$params[
'latest'] ) ) {
1271 $hdrs[
'x-newest'] =
'true';
1288 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1289 $statuses[$index] = $this->
newStatus(
'backend-fail-connect', $this->
name );
1296 $httpReqsByStage = [];
1297 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1299 $reqs = $fileOpHandle->httpOp;
1301 foreach ( $reqs
as $stage => &
$req ) {
1302 list( $container, $relPath ) =
$req[
'url'];
1304 $req[
'headers'] =
$req[
'headers'] ?? [];
1306 $httpReqsByStage[$stage][$index] =
$req;
1312 $reqCount =
count( $httpReqsByStage );
1313 for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1314 $httpReqs = $this->
http->runMulti( $httpReqsByStage[$stage] );
1315 foreach ( $httpReqs
as $index => $httpReq ) {
1317 $callback = $fileOpHandles[$index]->callback;
1318 $callback( $httpReq, $statuses[$index] );
1321 if ( !$statuses[$index]->isOK() ) {
1322 $stages =
count( $fileOpHandles[$index]->httpOp );
1323 for (
$s = ( $stage + 1 );
$s < $stages; ++
$s ) {
1324 unset( $httpReqsByStage[
$s][$index] );
1360 $status->fatal(
'backend-fail-connect', $this->
name );
1365 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1367 'url' => $this->
storageUrl( $auth, $container ),
1369 'x-container-read' => implode(
',',
$readUsers ),
1370 'x-container-write' => implode(
',',
$writeUsers )
1374 if ( $rcode != 204 && $rcode !== 202 ) {
1375 $status->fatal(
'backend-fail-internal', $this->
name );
1376 $this->logger->error( __METHOD__ .
': unexpected rcode value ({rcode})',
1377 [
'rcode' => $rcode ] );
1394 if ( $bypassCache ) {
1395 $this->containerStatCache->clear( $container );
1396 } elseif ( !$this->containerStatCache->hasField( $container,
'stat' ) ) {
1399 if ( !$this->containerStatCache->hasField( $container,
'stat' ) ) {
1405 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1407 'url' => $this->
storageUrl( $auth, $container ),
1411 if ( $rcode === 204 ) {
1413 'count' => $rhdrs[
'x-container-object-count'],
1414 'bytes' => $rhdrs[
'x-container-bytes-used']
1416 if ( $bypassCache ) {
1419 $this->containerStatCache->setField( $container,
'stat', $stat );
1422 } elseif ( $rcode === 404 ) {
1425 $this->
onError(
null, __METHOD__,
1426 [
'cont' => $container ], $rerr, $rcode, $rdesc );
1432 return $this->containerStatCache->getField( $container,
'stat' );
1447 $status->fatal(
'backend-fail-connect', $this->
name );
1453 if ( empty(
$params[
'noAccess'] ) ) {
1455 $readUsers = array_merge( $this->readUsers, [
'.r:*', $this->swiftUser ] );
1456 $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] );
1459 $readUsers = array_merge( $this->secureReadUsers, [ $this->swiftUser ] );
1460 $writeUsers = array_merge( $this->secureWriteUsers, [ $this->swiftUser ] );
1463 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1465 'url' => $this->
storageUrl( $auth, $container ),
1467 'x-container-read' => implode(
',',
$readUsers ),
1468 'x-container-write' => implode(
',',
$writeUsers )
1472 if ( $rcode === 201 ) {
1474 } elseif ( $rcode === 202 ) {
1495 $status->fatal(
'backend-fail-connect', $this->
name );
1500 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1501 'method' =>
'DELETE',
1502 'url' => $this->
storageUrl( $auth, $container ),
1506 if ( $rcode >= 200 && $rcode <= 299 ) {
1507 $this->containerStatCache->clear( $container );
1508 } elseif ( $rcode === 404 ) {
1510 } elseif ( $rcode === 409 ) {
1532 $fullCont,
$type, $limit, $after =
null, $prefix =
null, $delim =
null
1538 $status->fatal(
'backend-fail-connect', $this->
name );
1543 $query = [
'limit' => $limit ];
1544 if (
$type ===
'info' ) {
1545 $query[
'format'] =
'json';
1547 if ( $after !==
null ) {
1548 $query[
'marker'] = $after;
1550 if ( $prefix !==
null ) {
1551 $query[
'prefix'] = $prefix;
1553 if ( $delim !==
null ) {
1554 $query[
'delimiter'] = $delim;
1557 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1559 'url' => $this->
storageUrl( $auth, $fullCont ),
1564 $params = [
'cont' => $fullCont,
'prefix' => $prefix,
'delim' => $delim ];
1565 if ( $rcode === 200 ) {
1566 if (
$type ===
'info' ) {
1569 $status->value = explode(
"\n", trim( $rbody ) );
1571 } elseif ( $rcode === 204 ) {
1573 } elseif ( $rcode === 404 ) {
1583 foreach ( $containerInfo
as $container => $info ) {
1584 $this->containerStatCache->setField( $container,
'stat', $info );
1596 if ( $srcRel ===
null ) {
1597 $stats[
$path] =
false;
1599 } elseif ( !$auth ) {
1600 $stats[
$path] =
null;
1606 if ( $cstat ===
false ) {
1607 $stats[
$path] =
false;
1609 } elseif ( !is_array( $cstat ) ) {
1610 $stats[
$path] =
null;
1616 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1621 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1622 $reqs = $this->
http->runMulti( $reqs, $opts );
1625 if ( array_key_exists(
$path, $stats ) ) {
1629 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[
$path][
'response'];
1630 if ( $rcode === 200 || $rcode === 204 ) {
1632 if ( !empty(
$params[
'requireSHA1'] ) ) {
1637 if ( $this->isRGW ) {
1638 $stat[
'latest'] =
true;
1640 } elseif ( $rcode === 404 ) {
1644 $this->
onError(
null, __METHOD__,
$params, $rerr, $rcode, $rdesc );
1646 $stats[
$path] = $stat;
1660 $headers = $this->
sanitizeHdrs( [
'headers' => $rhdrs ] );
1666 'size' => isset( $rhdrs[
'content-length'] ) ? (int)$rhdrs[
'content-length'] : 0,
1667 'sha1' => $metadata[
'sha1base36'] ??
null,
1669 'md5' => ctype_xdigit( $rhdrs[
'etag'] ) ? $rhdrs[
'etag'] :
null,
1670 'xattr' => [
'metadata' => $metadata,
'headers' => $headers ]
1678 if ( $this->authErrorTimestamp !==
null ) {
1679 if ( ( time() - $this->authErrorTimestamp ) < 60 ) {
1682 $this->authErrorTimestamp =
null;
1688 if ( !$this->authCreds || $reAuth ) {
1689 $this->authSessionTimestamp = 0;
1691 $creds = $this->srvCache->get( $cacheKey );
1693 if ( isset( $creds[
'auth_token'] ) && isset( $creds[
'storage_url'] ) ) {
1694 $this->authCreds = $creds;
1696 $this->authSessionTimestamp = time() - ceil( $this->authTTL / 2 );
1698 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1700 'url' =>
"{$this->swiftAuthUrl}/v1.0",
1702 'x-auth-user' => $this->swiftUser,
1703 'x-auth-key' => $this->swiftKey
1707 if ( $rcode >= 200 && $rcode <= 299 ) {
1708 $this->authCreds = [
1709 'auth_token' => $rhdrs[
'x-auth-token'],
1710 'storage_url' => ( $this->swiftStorageUrl !==
null )
1711 ? $this->swiftStorageUrl
1712 : $rhdrs[
'x-storage-url']
1715 $this->srvCache->set( $cacheKey, $this->authCreds, ceil( $this->authTTL / 2 ) );
1716 $this->authSessionTimestamp = time();
1717 } elseif ( $rcode === 401 ) {
1718 $this->
onError(
null, __METHOD__, [],
"Authentication failed.", $rcode );
1719 $this->authErrorTimestamp = time();
1723 $this->
onError(
null, __METHOD__, [],
"HTTP return code: $rcode", $rcode );
1724 $this->authErrorTimestamp = time();
1730 if ( substr( $this->authCreds[
'storage_url'], -3 ) ===
'/v1' ) {
1731 $this->isRGW =
true;
1745 $parts = [ $creds[
'storage_url'] ];
1746 if ( strlen( $container ) ) {
1747 $parts[] = rawurlencode( $container );
1749 if ( strlen( $object ) ) {
1750 $parts[] = str_replace(
"%2F",
"/", rawurlencode( $object ) );
1753 return implode(
'/', $parts );
1761 return [
'x-auth-token' => $creds[
'auth_token'] ];
1771 return 'swiftcredentials:' . md5(
$username .
':' . $this->swiftAuthUrl );
1787 $status->fatal(
'backend-fail-internal', $this->
name );
1789 if (
$code == 401 ) {
1792 $msg =
"HTTP {code} ({desc}) in '{func}' (given '{req_params}')";
1801 $msgParams[
'err'] = $err;
1803 $this->logger->error( $msg, $msgParams );
1870 $this->container = $fullCont;
1872 if ( substr( $this->dir, -1 ) ===
'/' ) {
1873 $this->dir = substr( $this->dir, 0, -1 );
1875 if ( $this->dir ==
'' ) {
1876 $this->suffixStart = 0;
1878 $this->suffixStart = strlen( $this->dir ) + 1;
1896 next( $this->bufferIter );
1900 if ( !$this->
valid() &&
count( $this->bufferIter ) ) {
1902 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1912 $this->bufferAfter =
null;
1914 $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE, $this->params
1923 if ( $this->bufferIter ===
null ) {
1926 return ( current( $this->bufferIter ) !==
false );
1952 return substr(
current( $this->bufferIter ), $this->suffixStart, -1 );
1970 $relPath = substr(
$path, $this->suffixStart );
1971 if ( is_array( $stat ) ) {
1972 $storageDir = rtrim( $this->params[
'dir'],
'/' );
1973 $this->backend->loadListingStatInternal(
"$storageDir/$relPath", $stat );