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}/";
801 return ( count(
$status->value ) ) > 0;
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;
900 if ( count( $objects ) <
$limit ) {
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'] ) ) {
955 if ( count( $objects ) <
$limit ) {
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' ) {
1536 $status->value = FormatJson::decode( trim( $rbody ) );
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(
1757 "HTTP $code ($desc) in '{$func}' (given '" . FormatJson::encode(
$params ) .
"')" .
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 );
Apache License January http
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
interface is intended to be more or less compatible with the PHP memcached client.
A BagOStuff object with no objects in it.
File backend exception for checked exceptions (e.g.
FileBackendStore helper class for performing asynchronous file operations.
FileBackendStore $backend
Base class for all backends using particular storage medium.
setContainerCache( $container, array $val)
Set the cached info for a container.
executeOpHandlesInternal(array $fileOpHandles)
Execute a list of FileBackendStoreOpHandle handles in parallel.
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
resolveStoragePathReal( $storagePath)
Like resolveStoragePath() except null values are returned if the container is sharded and the shard c...
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
primeContainerCache(array $items)
Do a batch lookup from cache for container stats for all containers used in a list of container names...
deleteFileCache( $path)
Delete the cached stat info for a file path.
getContentType( $storagePath, $content, $fsPath)
Get the content type to use in HEAD/GET requests for a file.
fileExists(array $params)
Check if a file exists at a storage path in the backend.
getLocalCopy(array $params)
Get a local copy on disk of the file at a storage path in the backend.
string $name
Unique backend name.
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
newStatus()
Yields the result of the status wrapper callback on either:
scopedProfileSection( $section)
const ATTR_HEADERS
Bitfield flags for supported features.
Library for creating and parsing MW-style timestamps.
Class to handle concurrent HTTP requests.
Handles per process caching of items.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static send404Message( $fname, $flags=0)
Send out a standard 404 message for a file.
Iterator for listing directories.
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
Iterator for listing regular files.
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
SwiftFileBackend helper class to page through listings.
string $dir
Storage directory.
string $container
Container name.
__construct(SwiftFileBackend $backend, $fullCont, $dir, array $params)
string $bufferAfter
List items after this path.
pageFromList( $container, $dir, &$after, $limit, array $params)
Get the given list portion (page)
array $bufferIter
List of path or (path,stat array) entries.
SwiftFileBackend $backend
Class for an OpenStack Swift (or Ceph RGW) based file backend.
string $swiftUser
Swift user (account:user) to authenticate as.
sanitizeHdrs(array $params)
Sanitize and filter the custom headers from a $params array.
string $swiftAuthUrl
Authentication base URL (without version)
string $swiftTempUrlKey
Shared secret value for making temp URLs.
isPathUsableInternal( $storagePath)
Check if a file can be created or changed at a given storage path.
getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params)
Do not call this function outside of SwiftFileBackendFileList.
doPublishInternal( $fullCont, $dir, array $params)
doCreateInternal(array $params)
doGetFileStatMulti(array $params)
Get file stat information (concurrently if possible) for several files.
doGetFileSha1base36(array $params)
__construct(array $config)
doGetFileXAttributes(array $params)
onError( $status, $func, array $params, $err='', $code=0, $desc='')
Log an unexpected exception for this backend.
buildFileObjectListing(array $params, $dir, array $objects)
Build a list of file objects, filtering out any directories and extracting any stat info if provided ...
authTokenHeaders(array $creds)
getStatFromHeaders(array $rhdrs)
createContainer( $container, array $params)
Create a Swift container.
doCopyInternal(array $params)
getCredsCacheKey( $username)
Get the cache key for a container.
getDirectoryListInternal( $fullCont, $dir, array $params)
string $rgwS3AccessKey
S3 access key (RADOS Gateway)
getFileHttpUrl(array $params)
getCustomHeaders(array $rawHeaders)
int $authTTL
TTL in seconds.
headersFromParams(array $params)
Get headers to send to Swift when reading a file based on a FileBackend params array,...
getMetadata(array $rawHeaders)
bool $isRGW
Whether the server is an Ceph RGW.
addMissingMetadata(array $objHdrs, $path)
Fill in any missing object metadata and save it to Swift.
doStoreInternal(array $params)
int $authErrorTimestamp
UNIX timestamp.
getMetadataHeaders(array $rawHeaders)
int $authSessionTimestamp
UNIX timestamp.
loadListingStatInternal( $path, array $val)
Do not call this function outside of SwiftFileBackendFileList.
doPrepareInternal( $fullCont, $dir, array $params)
doSecureInternal( $fullCont, $dir, array $params)
getFileListInternal( $fullCont, $dir, array $params)
doMoveInternal(array $params)
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
deleteContainer( $container, array $params)
Delete a Swift container.
setContainerAccess( $container, array $readGrps, array $writeGrps)
Set read/write permissions for a Swift container.
doGetFileStat(array $params)
doGetLocalCopyMulti(array $params)
string $rgwS3SecretKey
S3 authentication key (RADOS Gateway)
doGetFileContentsMulti(array $params)
storageUrl(array $creds, $container=null, $object=null)
convertSwiftDate( $ts, $format=TS_MW)
Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT"/"2013-05-11T07:37:27.678360Z".
doStreamFile(array $params)
doPrimeContainerCache(array $containerInfo)
Fill the backend-specific process cache given an array of resolved container names and their correspo...
resolveContainerPath( $container, $relStoragePath)
Resolve a relative storage path, checking if it's allowed by the backend.
doCleanInternal( $fullCont, $dir, array $params)
ProcessCacheLRU $containerStatCache
Container stat cache.
getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params)
Do not call this function outside of SwiftFileBackendFileList.
string $swiftKey
Secret key for user.
doDirectoryExists( $fullCont, $dir, array $params)
objectListing( $fullCont, $type, $limit, $after=null, $prefix=null, $delim=null)
Get a list of objects under a container.
directoriesAreVirtual()
Is this a key/value store where directories are just virtual? Virtual directories exists in so much a...
doExecuteOpHandlesInternal(array $fileOpHandles)
doDeleteInternal(array $params)
doDescribeInternal(array $params)
getContainerStat( $container, $bypassCache=false)
Get a Swift container stat array, possibly from process cache.
array $httpOp
List of Requests for MultiHttpClient.
__construct(SwiftFileBackend $backend, Closure $callback, array $httpOp)
static factory( $prefix, $extension='', $tmpDirectory=null)
Make a new temporary file on the file system.
Multi-datacenter aware caching interface.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
this hook is for auditing only $req
the array() calling protocol came about after MediaWiki 1.4rc1.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
it s the revision text itself In either if gzip is the revision text is gzipped $flags
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
error also a ContextSource you ll probably need to make sure the header is varied on $request
this hook is for auditing only or null if authentication failed before getting that far $username
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
processing should stop and the error should be shown to the user * false
returning false will NOT prevent logging $e
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php