Robert Vogel has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/326915 )

Change subject: Fixing for REL1_27
......................................................................

Fixing for REL1_27

As https://gerrit.wikimedia.org/r/#/c/326127/ was a dead end, this is the
new approach to fix the extension for REL1_27

Change-Id: I38843ba186bbb3e620c0b2e7e252dbdf9827f477
---
M extension.json
M includes/DefaultSettings.php
M includes/NSFileRepoHooks.php
M includes/filebackend/NSFileRepoFSFileBackend.php
M includes/filerepo/file/NSLocalFile.php
A includes/utility/NSFileRepoHelper.php
A nsfr_img_auth.php
7 files changed, 341 insertions(+), 107 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/NSFileRepo 
refs/changes/15/326915/1

diff --git a/extension.json b/extension.json
index fd0fe0b..b7e5f46 100644
--- a/extension.json
+++ b/extension.json
@@ -27,7 +27,8 @@
                "NSLocalFile": "includes/filerepo/file/NSLocalFile.php",
                "NSLocalFileMoveBatch": 
"includes/filerepo/file/NSLocalFile.php",
                "NSOldLocalFile": "includes/filerepo/file/NSOldLocalFile.php",
-               "NSFileRepoFSFileBackend": 
"includes/filebackend/NSFileRepoFSFileBackend.php"
+               "NSFileRepoFSFileBackend": 
"includes/filebackend/NSFileRepoFSFileBackend.php",
+               "NSFileRepoHelper": "includes/utility/NSFileRepoHelper.php"
        },
        "ResourceModules": {
                "ext.nsfilerepo.special.upload": {
@@ -45,7 +46,10 @@
                "@note": "Note, this must be AFTER Extension:Lockdown has been 
included - thus assuming that the user has access to files in general + files 
at this particular namespace.",
                "userCan": "NSFileRepoHooks::onUserCan",
                "BeforePageDisplay": "NSFileRepoHooks::onBeforePageDisplay",
-               "UploadFormInitDescriptor": 
"NSFileRepoHooks::onUploadFormInitDescriptor"
+               "UploadFormInitDescriptor": 
"NSFileRepoHooks::onUploadFormInitDescriptor",
+               "ImgAuthBeforeCheckFileExists": 
"NSFileRepoHooks::onImgAuthBeforeCheckFileExists",
+               "ImgAuthBeforeStream": "NSFileRepoHooks::onImgAuthBeforeStream",
+               "UploadVerification": "NSFileRepoHooks::onUploadVerification"
        },
        "manifest_version": 1
 }
\ No newline at end of file
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 79041e9..2d43366 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -5,14 +5,8 @@
  */
 
 // Remove the default illegal char ':' - needed it to determine NS
-$GLOBALS['wgIllegalFileChars'] = str_replace( 
":","",$GLOBALS['wgIllegalFileChars'] );
+$GLOBALS['wgIllegalFileChars'] = str_replace( ":", "", 
$GLOBALS['wgIllegalFileChars'] );
 
-//Activate img_auth.php
-$GLOBALS['wgUploadPath'] = $GLOBALS['wgScriptPath'] .'/img_auth.php';
-$GLOBALS['wgImgAuthUrlPathMap']['/nsfilerepo/'] = 
'mwstore://nsfilerepo-fs/namespace/';
-$GLOBALS['wgFileBackends'][] = array(
-       'name'        => 'nsfilerepo-fs',
-       'class'       => 'NSFileRepoFSFileBackend',
-       'lockManager' => 'fsLockManager',
-       #'wikiId'       => wfWikiID()
-);
\ No newline at end of file
+//Activate "nsfr_img_auth.php"
+//This may be obsolete in future MW versions
+$GLOBALS['wgUploadPath'] = $GLOBALS['wgScriptPath'] .'/nsfr_img_auth.php';
\ No newline at end of file
diff --git a/includes/NSFileRepoHooks.php b/includes/NSFileRepoHooks.php
index 1702be0..86a4ed7 100644
--- a/includes/NSFileRepoHooks.php
+++ b/includes/NSFileRepoHooks.php
@@ -12,9 +12,6 @@
                }
 
                $GLOBALS['wgLocalFileRepo']['class'] = "NSLocalRepo";
-               $GLOBALS['wgLocalFileRepo']['backend'] = "nsfilerepo-fs";
-               $GLOBALS['wgLocalFileRepo']['url'] = $GLOBALS['wgScriptPath'] 
.'/img_auth.php';
-
                RepoGroup::destroySingleton();
        }
 
@@ -37,12 +34,47 @@
        }
 
        /**
+        * @param $path
+        * @param $name
+        * @param $filename
+        * @return bool
+        */
+       public static function onImgAuthBeforeCheckFileExists( &$path, &$name, 
&$filename ) {
+               $nsfrhelper = new NSFileRepoHelper();
+               $title = $nsfrhelper->getTitleFromPath( $path );
+               if( $title instanceof Title ) {
+                       $name = $title->getPrefixedDBkey();
+               }
+
+               return true;
+       }
+
+       /**
+        * @param Title $title
+        * @param $path
+        * @param $name
+        * @param $result
+        * @return bool
+        */
+       public static function onImgAuthBeforeStream( &$title, &$path, &$name, 
&$result ) {
+               $nsfrhelper = new NSFileRepoHelper();
+               $title = $nsfrhelper->getTitleFromPath( $path );
+
+               if( $title instanceof Title === false ) {
+                       $result = array('img-auth-accessdenied', 
'img-auth-badtitle', $name);
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
         * Check for Namespace in Title line
         * @param UploadForm $uploadForm
         * @return boolean
         */
        public static function onUploadFormBeforeProcessing( &$uploadForm ) {
-               $title = Title::newFromText($uploadForm->mDesiredDestName);
+               $title = Title::newFromText( $uploadForm->mDesiredDestName );
                if( $title === null ) {
                        return true;
                }
@@ -57,7 +89,7 @@
        }
 
        /**
-        * If Extension:Lockdown has been activated (recommend), check 
individual namespace protection
+        * Check individual namespace protection using Extension:Lockdown
         * @global array $wgWhitelistRead
         * @param Title $title
         * @param user $user
@@ -69,16 +101,23 @@
                global $wgWhitelistRead;
                if ( $wgWhitelistRead !== false && in_array( 
$title->getPrefixedText(), $wgWhitelistRead ) ) {
                        return true;
-               } elseif( function_exists( 'lockdownUserPermissionsErrors' ) ) {
-                       if( $title->getNamespace() == NS_FILE ) {
-                               $ntitle = Title::newFromText( 
$title->mDbkeyform );
-                               $ret_val = ( $ntitle->getNamespace() < 100 ) ?
-                                               true : 
lockdownUserPermissionsErrors( $ntitle, $user, $action, $result );
-                               $result = null;
-                               return $ret_val;
-                       }
                }
-               return true;
+
+               if( $title->getNamespace() !== NS_FILE ) {
+                       return true;
+               }
+
+               $ntitle = Title::newFromText( $title->getDBkey() );
+               $ret_val = true;
+
+               //Additional check for NS_MAIN: If a user is not allowed to 
read NS_MAIN he should also be not allowed
+               //to view files with no namespace-prefix as they are logically 
assigned to namespace NS_MAIN
+               if( $ntitle->getNamespace() < 100 || $ntitle->getNamespace() 
=== NS_MAIN ) {
+                       $ret_val = lockdownUserPermissionsErrors( $ntitle, 
$user, $action, $result );
+               }
+
+               $result = null;
+               return $ret_val;
        }
 
        /**
@@ -187,4 +226,20 @@
                }
                return $aReturn;
        }
+
+       /**
+        * Checks if the destination file name contains a valid namespace prefix
+        * @param string $destName
+        * @param string $tempPath
+        * @param string $error
+        * @return bool
+        */
+       public static function onUploadVerification( $destName, $tempPath, 
&$error ) {
+               $title = Title::newFromText( $destName );
+               if( strpos( $title->getText(), ':' ) !== false ) { //There is a 
colon in the name but it was not a valid namespace prefix!
+                       $error = 'illegal-filename';
+                       return false;
+               }
+               return true;
+       }
 }
\ No newline at end of file
diff --git a/includes/filebackend/NSFileRepoFSFileBackend.php 
b/includes/filebackend/NSFileRepoFSFileBackend.php
index ed2e0f9..0cf7ad9 100644
--- a/includes/filebackend/NSFileRepoFSFileBackend.php
+++ b/includes/filebackend/NSFileRepoFSFileBackend.php
@@ -2,88 +2,13 @@
 
 class NSFileRepoFSFileBackend extends FSFileBackend {
 
-       protected $zoneSuffixes = [
-               'public', 'thumb', 'transcoded', 'deleted', 'archive', 'temp'
-       ];
-
        /**
-        * This is a pretty bad workaround for "img_auth.php" and
-        * "FileRepo/FileBackend" not being able to do proper permission checks 
in
-        * REL1_27 of MediaWiki
-        * For details see: https://phabricator.wikimedia.org/T140334
-        * @param array $params
-        * @return boolean
-        */
-       protected function doGetFileStat( array $params ) {
-               $unprefixedPath = $this->stripStorageBasePath( $params['src'] );
-               //e.g. $unprefixedPath = "5000/b/be/Some_image.png"
-               //  or $unprefixedPath = 
"thumb/5000/b/be/Some_image.png/300px-Some_image.png"
-               //  or $unprefixedPath = "deleted/5000/b/be/Some_image.png"
-               //  or $unprefixedPath = "archive/5000/b/be/Some_image.png"
-               //  or $unprefixedPath = "temp/5000/b/be/Some_image.png"
-               //  or $unprefixedPath = 
"4/03/Some_image_that_is_not_in_a_namespace.png"
-
-               $parts = explode( '/', $unprefixedPath );
-               if( count($parts) < 4 ) {
-                       return parent::doGetFileStat($params);
-               }
-
-               if( in_array( $parts[0], $this->zoneSuffixes ) ) {
-                       array_shift( $parts );
-               }
-
-               $namespaceId = intval( array_shift( $parts ) ); // = 5000
-               $fileName = array_pop( $parts ); // = "Some_image.png" or 
"300px-Some_image.png"
-               if( UploadBase::isThumbName( $fileName ) ) {
-                       //HINT: Thumbname-to-filename-conversion taken from 
includes/Upload/UploadBase.php
-                       //Check for filenames like 50px- or 180px-, these are 
mostly thumbnails
-                       $fileName = substr( $fileName , strpos( $fileName , '-' 
) +1 );
-               }
-               $title = Title::makeTitle( (int)$namespaceId, $fileName );
-               if( $title instanceof Title && !$title->userCan( 'read' ) ) {
-                       return false;
-               }
-
-               //Maybe the single file is not protectey by Extension:Lockdown, 
but
-               //also by some other mechanism
-               $actualFileTitle = Title::makeTitle( NS_FILE, 
$title->getPrefixedText() );
-               if( $actualFileTitle instanceof Title && 
!$actualFileTitle->userCan( 'read' ) ) {
-                       return false;
-               }
-
-               return parent::doGetFileStat( $params );
-       }
-
-       protected function resolveToFSPath( $storagePath ) {
-               global $wgUploadDirectory;
-               $unprefixedPath = $this->stripStorageBasePath( $storagePath );
-               return "$wgUploadDirectory/$unprefixedPath";
-       }
-
-       /**
+        * Enables support for Non-ASCII filenames event on Windows. As we 
always deliver through a PHP script
+        * (e.g. img_auth.php) the encoding issue of PHP on the Windows FS is 
not a problem anymore.
         * @see FSFileBackend::getFeatures()
         * @return int
         */
        public function getFeatures() {
                return FileBackend::ATTR_UNICODE_PATHS;
-       }
-
-       protected function stripStorageBasePath( $storagePath ) {
-               global $wgImgAuthUrlPathMap;
-               /**
-                * TODO: Improve this!
-                * A proper FileBackendConfiguration should be used!
-                */
-               foreach( $this->zoneSuffixes as $zone ) {
-                       $parts = explode( "$zone/nsfilerepo/", $storagePath, 2 
);
-                       if( count( $parts ) === 2 ) {
-                               if( $zone === 'public' ) {
-                                       return $parts[1];
-                               }
-                               return $zone.'/'.$parts[1];
-                       }
-               }
-
-               return preg_replace( 
"#^{$wgImgAuthUrlPathMap['/nsfilerepo/']}#", '', $storagePath );
        }
 }
\ No newline at end of file
diff --git a/includes/filerepo/file/NSLocalFile.php 
b/includes/filerepo/file/NSLocalFile.php
index d39b69d..d2b1222 100644
--- a/includes/filerepo/file/NSLocalFile.php
+++ b/includes/filerepo/file/NSLocalFile.php
@@ -325,10 +325,6 @@
                        return false;
                }
        }
-
-       public function getHashPath() {
-               return 'nsfilerepo/' . parent::getHashPath();
-       }
 }
 
 /**
diff --git a/includes/utility/NSFileRepoHelper.php 
b/includes/utility/NSFileRepoHelper.php
new file mode 100644
index 0000000..f620ac5
--- /dev/null
+++ b/includes/utility/NSFileRepoHelper.php
@@ -0,0 +1,36 @@
+<?php
+
+class NSFileRepoHelper {
+       protected $pathRegEx = 
'#\\/(thumb\\/|archive\\/|deleted\\/)?(\d*?)?\\/[a-f0-9]{1}\\/[a-f0-9]{2}\\/(.*?)$#';
+
+       /**
+        * Returns a Title object that can be used to check permissions 
against. ATTENTION: It will _not_ return a
+        * Title from NS_FILE, but either from NS_MAIN, or from the specific 
namespace that was found in the path!
+        * Examples for $path:
+        * /thumb/1502/7/78/Some_File.png/300px-Some_File.png
+        * /1502/7/78/Some_File.png
+        * @param $path
+        * @return null|Title
+        */
+       public function getTitleFromPath( $path ) {
+               error_log( $path );
+               $filename = wfBaseName( $path );
+               if( UploadBase::isThumbName( $filename ) ) {
+                       //HINT: Thumbname-to-filename-conversion taken from 
includes/Upload/UploadBase.php
+                       //Check for filenames like 50px- or 180px-, these are 
mostly thumbnails
+                       $filename = substr( $filename , strpos( $filename , '-' 
) +1 );
+               }
+
+               $title = Title::newFromText( $filename );
+
+               $matches = array();
+               preg_match( $this->pathRegEx , $path, $matches );
+               if( empty( $matches[2] ) ) { //Not a file from a namespace?
+                       return $title;
+               }
+
+               $title = Title::makeTitleSafe( (int)$matches[2], $filename );
+               error_log( $title->getPrefixedDBkey() );
+               return $title;
+       }
+}
\ No newline at end of file
diff --git a/nsfr_img_auth.php b/nsfr_img_auth.php
new file mode 100644
index 0000000..33e5423
--- /dev/null
+++ b/nsfr_img_auth.php
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * THIS SCRIPT IS A COPY OF MEDIAWIKI CORE img_auth.php ENTRY POINT
+ * It has been altered to enable NSFileRepo functionality in REL1_27
+ */
+
+/**
+ * Image authorisation script
+ *
+ * To use this, see https://www.mediawiki.org/wiki/Manual:Image_Authorization
+ *
+ * - Set $wgUploadDirectory to a non-public directory (not web accessible)
+ * - Set $wgUploadPath to point to this file
+ *
+ * Optional Parameters
+ *
+ * - Set $wgImgAuthDetails = true if you want the reason the access was denied 
messages to
+ *       be displayed instead of just the 403 error (doesn't work on IE 
anyway),
+ *       otherwise it will only appear in error logs
+ *
+ *  For security reasons, you usually don't want your user to know *why* 
access was denied,
+ *  just that it was. If you want to change this, you can set 
$wgImgAuthDetails to 'true'
+ *  in localsettings.php and it will give the user the reason why access was 
denied.
+ *
+ * Your server needs to support PATH_INFO; CGI-based configurations usually 
don't.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
+
+$baseDir = dirname( $_SERVER['SCRIPT_FILENAME'] );
+chdir( $baseDir );
+require ( $baseDir . '/includes/WebStart.php' );
+unset( $baseDir );
+//require ( __DIR__ . '/includes/WebStart.php' ); //This would be if we didn't 
work with a symlink
+
+# Set action base paths so that WebRequest::getPathInfo()
+# recognizes the "X" as the 'title' in ../img_auth.php/X urls.
+$wgArticlePath = false; # Don't let a "/*" article path clober our action path
+$wgActionPaths = [ "$wgUploadPath/" ];
+
+wfImageAuthMain();
+
+$mediawiki = new MediaWiki();
+$mediawiki->doPostOutputShutdown( 'fast' );
+
+function wfImageAuthMain() {
+       global $wgImgAuthUrlPathMap;
+
+       $request = RequestContext::getMain()->getRequest();
+
+       // Get the requested file path (source file or thumbnail)
+       $matches = WebRequest::getPathInfo();
+       if ( !isset( $matches['title'] ) ) {
+               wfForbidden( 'img-auth-accessdenied', 'img-auth-nopathinfo' );
+               return;
+       }
+       $path = $matches['title'];
+       if ( $path && $path[0] !== '/' ) {
+               // Make sure $path has a leading /
+               $path = "/" . $path;
+       }
+
+       // Check for bug 28235: QUERY_STRING overriding the correct extension
+       $whitelist = [];
+       $extension = FileBackend::extensionFromPath( $path, 'rawcase' );
+       if ( $extension != '' ) {
+               $whitelist[] = $extension;
+       }
+       if ( !$request->checkUrlExtension( $whitelist ) ) {
+               return;
+       }
+
+       // Various extensions may have their own backends that need access.
+       // Check if there is a special backend and storage base path for this 
file.
+       foreach ( $wgImgAuthUrlPathMap as $prefix => $storageDir ) {
+               $prefix = rtrim( $prefix, '/' ) . '/'; // implicit trailing 
slash
+               if ( strpos( $path, $prefix ) === 0 ) {
+                       $be = FileBackendGroup::singleton()->backendFromPath( 
$storageDir );
+                       $filename = $storageDir . substr( $path, strlen( 
$prefix ) ); // strip prefix
+                       // Check basic user authorization
+                       if ( !RequestContext::getMain()->getUser()->isAllowed( 
'read' ) ) {
+                               wfForbidden( 'img-auth-accessdenied', 
'img-auth-noread', $path );
+                               return;
+                       }
+                       if ( $be->fileExists( [ 'src' => $filename ] ) ) {
+                               wfDebugLog( 'img_auth', "Streaming `" . 
$filename . "`." );
+                               $be->streamFile( [ 'src' => $filename ],
+                                       [ 'Cache-Control: private', 'Vary: 
Cookie' ] );
+                       } else {
+                               wfForbidden( 'img-auth-accessdenied', 
'img-auth-nofile', $path );
+                       }
+                       return;
+               }
+       }
+
+       // Get the local file repository
+       $repo = RepoGroup::singleton()->getRepo( 'local' );
+       $zone = strstr( ltrim( $path, '/' ), '/', true );
+
+       // Get the full file storage path and extract the source file name.
+       // (e.g. 120px-Foo.png => Foo.png or page2-120px-Foo.png => Foo.png).
+       // This only applies to thumbnails/transcoded, and each of them should
+       // be under a folder that has the source file name.
+       if ( $zone === 'thumb' || $zone === 'transcoded' ) {
+               $name = wfBaseName( dirname( $path ) );
+               $filename = $repo->getZonePath( $zone ) . substr( $path, 
strlen( "/" . $zone ) );
+               Hooks::run( 'ImgAuthBeforeCheckFileExists', [ &$path, &$name, 
&$filename ] );
+               // Check to see if the file exists
+               if ( !$repo->fileExists( $filename ) ) {
+                       wfForbidden( 'img-auth-accessdenied', 
'img-auth-nofile', $filename );
+                       return;
+               }
+       } else {
+               $name = wfBaseName( $path ); // file is a source file
+               $filename = $repo->getZonePath( 'public' ) . $path;
+               Hooks::run( 'ImgAuthBeforeCheckFileExists', [ &$path, &$name, 
&$filename ] );
+               // Check to see if the file exists and is not deleted
+               $bits = explode( '!', $name, 2 );
+               if ( substr( $path, 0, 9 ) === '/archive/' && count( $bits ) == 
2 ) {
+                       $file = $repo->newFromArchiveName( $bits[1], $name );
+               } else {
+                       $file = $repo->newFile( $name );
+               }
+               if ( !$file->exists() || $file->isDeleted( File::DELETED_FILE ) 
) {
+                       wfForbidden( 'img-auth-accessdenied', 
'img-auth-nofile', $filename );
+                       return;
+               }
+       }
+
+       $headers = []; // extra HTTP headers to send
+
+       // For private wikis, run extra auth checks and set cache control 
headers
+       $headers[] = 'Cache-Control: private';
+       $headers[] = 'Vary: Cookie';
+
+       $title = Title::makeTitleSafe( NS_FILE, $name );
+       if ( !$title instanceof Title ) { // files have valid titles
+               wfForbidden( 'img-auth-accessdenied', 'img-auth-badtitle', 
$name );
+               return;
+       }
+
+       // Run hook for extension authorization plugins
+       /** @var $result array */
+       $result = null;
+       if ( !Hooks::run( 'ImgAuthBeforeStream', [ &$title, &$path, &$name, 
&$result ] ) ) {
+               wfForbidden( $result[0], $result[1], array_slice( $result, 2 ) 
);
+               return;
+       }
+
+       // Check user authorization for this title
+       // Checks Whitelist too
+       if ( !$title->userCan( 'read' ) ) {
+               wfForbidden( 'img-auth-accessdenied', 'img-auth-noread', $name 
);
+               return;
+       }
+
+       if ( $request->getCheck( 'download' ) ) {
+               $headers[] = 'Content-Disposition: attachment';
+       }
+
+       // Stream the requested file
+       wfDebugLog( 'img_auth', "Streaming `" . $filename . "`." );
+       $repo->streamFile( $filename, $headers );
+}
+
+/**
+ * Issue a standard HTTP 403 Forbidden header ($msg1-a message index, not a 
message) and an
+ * error message ($msg2, also a message index), (both required) then end the 
script
+ * subsequent arguments to $msg2 will be passed as parameters only for 
replacing in $msg2
+ * @param string $msg1
+ * @param string $msg2
+ */
+function wfForbidden( $msg1, $msg2 ) {
+       global $wgImgAuthDetails;
+
+       $args = func_get_args();
+       array_shift( $args );
+       array_shift( $args );
+       $args = ( isset( $args[0] ) && is_array( $args[0] ) ) ? $args[0] : 
$args;
+
+       $msgHdr = wfMessage( $msg1 )->escaped();
+       $detailMsgKey = $wgImgAuthDetails ? $msg2 : 'badaccess-group0';
+       $detailMsg = wfMessage( $detailMsgKey, $args )->escaped();
+
+       wfDebugLog( 'img_auth',
+               "wfForbidden Hdr: " . wfMessage( $msg1 )->inLanguage( 'en' 
)->text() . " Msg: " .
+                       wfMessage( $msg2, $args )->inLanguage( 'en' )->text()
+       );
+
+       HttpStatus::header( 403 );
+       header( 'Cache-Control: no-cache' );
+       header( 'Content-Type: text/html; charset=utf-8' );
+       echo <<<ENDS
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8" />
+<title>$msgHdr</title>
+</head>
+<body>
+<h1>$msgHdr</h1>
+<p>$detailMsg</p>
+</body>
+</html>
+ENDS;
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/326915
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I38843ba186bbb3e620c0b2e7e252dbdf9827f477
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/NSFileRepo
Gerrit-Branch: master
Gerrit-Owner: Robert Vogel <vo...@hallowelt.biz>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to