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 if ( !isset( $stat[
'sha1'] ) ) {
1063 return $stat[
'sha1'];
1075 if ( $srcRel ===
null ) {
1104 if ( empty(
$params[
'allowOB'] ) ) {
1109 $handle = fopen(
'php://output',
'wb' );
1110 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1112 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1115 'stream' => $handle,
1116 'flags' => [
'relayResponseHeaders' => empty(
$params[
'headless'] ) ]
1119 if ( $rcode >= 200 && $rcode <= 299 ) {
1121 } elseif ( $rcode === 404 ) {
1141 $ep = array_diff_key(
$params, [
'srcs' => 1 ] );
1148 if ( $srcRel ===
null || !$auth ) {
1149 $tmpFiles[
$path] =
null;
1157 $handle = fopen( $tmpFile->getPath(),
'wb' );
1161 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1164 'stream' => $handle,
1170 $tmpFiles[
$path] = $tmpFile;
1173 $isLatest = ( $this->isRGW || !empty(
$params[
'latest'] ) );
1174 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1175 $reqs = $this->
http->runMulti( $reqs, $opts );
1176 foreach ( $reqs
as $path => $op ) {
1177 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $op[
'response'];
1178 fclose( $op[
'stream'] );
1179 if ( $rcode >= 200 && $rcode <= 299 ) {
1180 $size = $tmpFiles[
$path] ? $tmpFiles[
$path]->getSize() : 0;
1182 if ( $size != $rhdrs[
'content-length'] ) {
1183 $tmpFiles[
$path] =
null;
1184 $rerr =
"Got {$size}/{$rhdrs['content-length']} bytes";
1185 $this->
onError(
null, __METHOD__,
1186 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1190 $stat[
'latest'] = $isLatest;
1191 $this->cheapCache->setField(
$path,
'stat', $stat );
1192 } elseif ( $rcode === 404 ) {
1193 $tmpFiles[
$path] =
false;
1195 $tmpFiles[
$path] =
null;
1196 $this->
onError(
null, __METHOD__,
1197 [
'src' =>
$path ] + $ep, $rerr, $rcode, $rdesc );
1205 if ( $this->swiftTempUrlKey !=
'' ||
1206 ( $this->rgwS3AccessKey !=
'' && $this->rgwS3SecretKey !=
'' )
1209 if ( $srcRel ===
null ) {
1218 $ttl =
$params[
'ttl'] ?? 86400;
1219 $expires = time() + $ttl;
1221 if ( $this->swiftTempUrlKey !=
'' ) {
1222 $url = $this->
storageUrl( $auth, $srcCont, $srcRel );
1224 $contPath = parse_url( $this->
storageUrl( $auth, $srcCont ), PHP_URL_PATH );
1225 $signature = hash_hmac(
'sha1',
1226 "GET\n{$expires}\n{$contPath}/{$srcRel}",
1227 $this->swiftTempUrlKey
1230 return "{$url}?temp_url_sig={$signature}&temp_url_expires={$expires}";
1233 $spath =
'/' . rawurlencode( $srcCont ) .
'/' .
1234 str_replace(
'%2F',
'/', rawurlencode( $srcRel ) );
1236 $signature = base64_encode( hash_hmac(
1238 "GET\n\n\n{$expires}\n{$spath}",
1239 $this->rgwS3SecretKey,
1245 return str_replace(
'/swift/v1',
'', $this->
storageUrl( $auth ) . $spath ) .
1248 'Signature' => $signature,
1249 'Expires' => $expires,
1250 'AWSAccessKeyId' => $this->rgwS3AccessKey
1272 if ( !empty(
$params[
'latest'] ) ) {
1273 $hdrs[
'x-newest'] =
'true';
1290 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1291 $statuses[$index] = $this->
newStatus(
'backend-fail-connect', $this->
name );
1298 $httpReqsByStage = [];
1299 foreach ( $fileOpHandles
as $index => $fileOpHandle ) {
1301 $reqs = $fileOpHandle->httpOp;
1303 foreach ( $reqs
as $stage => &
$req ) {
1304 list( $container, $relPath ) =
$req[
'url'];
1306 $req[
'headers'] =
$req[
'headers'] ?? [];
1308 $httpReqsByStage[$stage][$index] =
$req;
1314 $reqCount =
count( $httpReqsByStage );
1315 for ( $stage = 0; $stage < $reqCount; ++$stage ) {
1316 $httpReqs = $this->
http->runMulti( $httpReqsByStage[$stage] );
1317 foreach ( $httpReqs
as $index => $httpReq ) {
1319 $callback = $fileOpHandles[$index]->callback;
1320 $callback( $httpReq, $statuses[$index] );
1323 if ( !$statuses[$index]->isOK() ) {
1324 $stages =
count( $fileOpHandles[$index]->httpOp );
1325 for (
$s = ( $stage + 1 );
$s < $stages; ++
$s ) {
1326 unset( $httpReqsByStage[
$s][$index] );
1362 $status->fatal(
'backend-fail-connect', $this->
name );
1367 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1369 'url' => $this->
storageUrl( $auth, $container ),
1371 'x-container-read' => implode(
',',
$readUsers ),
1372 'x-container-write' => implode(
',',
$writeUsers )
1376 if ( $rcode != 204 && $rcode !== 202 ) {
1377 $status->fatal(
'backend-fail-internal', $this->
name );
1378 $this->logger->error( __METHOD__ .
': unexpected rcode value ({rcode})',
1379 [
'rcode' => $rcode ] );
1396 if ( $bypassCache ) {
1397 $this->containerStatCache->clear( $container );
1398 } elseif ( !$this->containerStatCache->hasField( $container,
'stat' ) ) {
1401 if ( !$this->containerStatCache->hasField( $container,
'stat' ) ) {
1407 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1409 'url' => $this->
storageUrl( $auth, $container ),
1413 if ( $rcode === 204 ) {
1415 'count' => $rhdrs[
'x-container-object-count'],
1416 'bytes' => $rhdrs[
'x-container-bytes-used']
1418 if ( $bypassCache ) {
1421 $this->containerStatCache->setField( $container,
'stat', $stat );
1424 } elseif ( $rcode === 404 ) {
1427 $this->
onError(
null, __METHOD__,
1428 [
'cont' => $container ], $rerr, $rcode, $rdesc );
1434 return $this->containerStatCache->getField( $container,
'stat' );
1449 $status->fatal(
'backend-fail-connect', $this->
name );
1455 if ( empty(
$params[
'noAccess'] ) ) {
1457 $readUsers = array_merge( $this->readUsers, [
'.r:*', $this->swiftUser ] );
1458 $writeUsers = array_merge( $this->writeUsers, [ $this->swiftUser ] );
1461 $readUsers = array_merge( $this->secureReadUsers, [ $this->swiftUser ] );
1462 $writeUsers = array_merge( $this->secureWriteUsers, [ $this->swiftUser ] );
1465 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1467 'url' => $this->
storageUrl( $auth, $container ),
1469 'x-container-read' => implode(
',',
$readUsers ),
1470 'x-container-write' => implode(
',',
$writeUsers )
1474 if ( $rcode === 201 ) {
1476 } elseif ( $rcode === 202 ) {
1497 $status->fatal(
'backend-fail-connect', $this->
name );
1502 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1503 'method' =>
'DELETE',
1504 'url' => $this->
storageUrl( $auth, $container ),
1508 if ( $rcode >= 200 && $rcode <= 299 ) {
1509 $this->containerStatCache->clear( $container );
1510 } elseif ( $rcode === 404 ) {
1512 } elseif ( $rcode === 409 ) {
1534 $fullCont,
$type, $limit, $after =
null, $prefix =
null, $delim =
null
1540 $status->fatal(
'backend-fail-connect', $this->
name );
1545 $query = [
'limit' => $limit ];
1546 if (
$type ===
'info' ) {
1547 $query[
'format'] =
'json';
1549 if ( $after !==
null ) {
1550 $query[
'marker'] = $after;
1552 if ( $prefix !==
null ) {
1553 $query[
'prefix'] = $prefix;
1555 if ( $delim !==
null ) {
1556 $query[
'delimiter'] = $delim;
1559 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->
http->run( [
1561 'url' => $this->
storageUrl( $auth, $fullCont ),
1566 $params = [
'cont' => $fullCont,
'prefix' => $prefix,
'delim' => $delim ];
1567 if ( $rcode === 200 ) {
1568 if (
$type ===
'info' ) {
1571 $status->value = explode(
"\n", trim( $rbody ) );
1573 } elseif ( $rcode === 204 ) {
1575 } elseif ( $rcode === 404 ) {
1585 foreach ( $containerInfo
as $container => $info ) {
1586 $this->containerStatCache->setField( $container,
'stat', $info );
1598 if ( $srcRel ===
null ) {
1599 $stats[
$path] =
false;
1601 } elseif ( !$auth ) {
1602 $stats[
$path] =
null;
1608 if ( $cstat ===
false ) {
1609 $stats[
$path] =
false;
1611 } elseif ( !is_array( $cstat ) ) {
1612 $stats[
$path] =
null;
1618 'url' => $this->
storageUrl( $auth, $srcCont, $srcRel ),
1623 $opts = [
'maxConnsPerHost' =>
$params[
'concurrency'] ];
1624 $reqs = $this->
http->runMulti( $reqs, $opts );
1627 if ( array_key_exists(
$path, $stats ) ) {
1631 list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $reqs[
$path][
'response'];
1632 if ( $rcode === 200 || $rcode === 204 ) {
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 );