Example #1
0
 /**
  * Delete files in the deleted directory if they are not referenced in the
  * filearchive table. This needs to be done in the repo because it needs to
  * interleave database locks with file operations, which is potentially a
  * remote operation.
  * @return FileRepoStatus
  */
 function cleanupDeletedBatch($storageKeys)
 {
     $root = $this->getZonePath('deleted');
     $dbw = $this->getMasterDB();
     $status = $this->newGood();
     $storageKeys = array_unique($storageKeys);
     foreach ($storageKeys as $key) {
         $hashPath = $this->getDeletedHashPath($key);
         $path = "{$root}/{$hashPath}{$key}";
         $dbw->begin();
         $inuse = $dbw->selectField('filearchive', '1', array('fa_storage_group' => 'deleted', 'fa_storage_key' => $key), __METHOD__, array('FOR UPDATE'));
         if (!$inuse) {
             $sha1 = substr($key, 0, strcspn($key, '.'));
             $ext = substr($key, strcspn($key, '.') + 1);
             $ext = File::normalizeExtension($ext);
             $inuse = $dbw->selectField('oldimage', '1', array('oi_sha1' => $sha1, 'oi_archive_name ' . $dbw->buildLike($dbw->anyString(), ".{$ext}"), $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE), __METHOD__, array('FOR UPDATE'));
         }
         if (!$inuse) {
             wfDebug(__METHOD__ . ": deleting {$key}\n");
             if (!@unlink($path)) {
                 $status->error('undelete-cleanup-error', $path);
                 $status->failCount++;
             }
         } else {
             wfDebug(__METHOD__ . ": {$key} still in use\n");
             $status->successCount++;
         }
         $dbw->commit();
     }
     return $status;
 }
Example #2
0
	function execute() {
		global $wgRequest;

		if ( !$wgRequest->wasPosted() ) {
			echo $this->dtd();
			echo <<<EOT
<html>
<head><title>store.php Test Interface</title></head>
<body>
<form method="post" action="store.php" enctype="multipart/form-data" >
<p>File: <input type="file" name="file"/></p>
<p><input type="submit" value="OK"/></p>
</form></body></html>
EOT;
			return true;
		}

		$srcFile = $wgRequest->getFileTempname( 'file' );
		if ( !$srcFile ) {
			$this->error( 400, 'webstore_no_file' );
			return false;
		}

		// Use an hourly timestamped directory for easy cleanup
		$now = time();
		$this->cleanupTemp( $now );

		$timestamp = gmdate( self::$tempDirFormat, $now );
		if ( !wfMkdirParents( "{$this->tmpDir}/$timestamp", null, __METHOD__ ) ) {
			$this->error( 500, 'webstore_dest_mkdir' );
			return false;
		}

		// Get the extension of the upload, needs to be preserved for type detection
		$name = $wgRequest->getFileName( 'file' );
		$n = strrpos( $name, '.' );
		if ( $n ) {
			$extension = '.' . File::normalizeExtension( substr( $name, $n + 1 ) );
		} else {
			$extension = '';
		}

		// Pick a random temporary path
		$destRel =  $timestamp . '/' . md5( mt_rand() . mt_rand() . mt_rand() ) . $extension;
		if ( !@move_uploaded_file( $srcFile, "{$this->tmpDir}/$destRel" ) ) {
			$this->error( 400, 'webstore_move_uploaded', $srcFile, "{$this->tmpDir}/$destRel" );
			return false;
		}

		// Succeeded, return temporary location
		header( 'Content-Type: text/xml' );
		echo <<<EOT
<?xml version="1.0" encoding="utf-8"?>
<response>
<location>$destRel</location>
</response>
EOT;
		return true;
	}
Example #3
0
 /**
  * Check if a hidden (revision delete) file has this sha1 key
  *
  * @param string $key File storage key (base-36 sha1 key with file extension)
  * @param string|null $lock Use "lock" to lock the row via FOR UPDATE
  * @return bool File with this key is in use
  */
 protected function hiddenFileHasKey($key, $lock = null)
 {
     $options = $lock === 'lock' ? array('FOR UPDATE') : array();
     $sha1 = self::getHashFromKey($key);
     $ext = File::normalizeExtension(substr($key, strcspn($key, '.') + 1));
     $dbw = $this->getMasterDB();
     return (bool) $dbw->selectField('oldimage', '1', array('oi_sha1' => $sha1, 'oi_archive_name ' . $dbw->buildLike($dbw->anyString(), ".{$ext}"), $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE), __METHOD__, $options);
 }
 /**
  * Find or guess extension -- ensuring that our extension matches our mime type.
  * Since these files are constructed from php tempnames they may not start off
  * with an extension.
  * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
  * uploads versus the desired filename. Maybe we can get that passed to us...
  */
 public static function getExtensionForPath($path)
 {
     // Does this have an extension?
     $n = strrpos($path, '.');
     $extension = null;
     if ($n !== false) {
         $extension = $n ? substr($path, $n + 1) : '';
     } else {
         // If not, assume that it should be related to the mime type of the original file.
         $magic = MimeMagic::singleton();
         $mimeType = $magic->guessMimeType($path);
         $extensions = explode(' ', MimeMagic::singleton()->getExtensionsForType($mimeType));
         if (count($extensions)) {
             $extension = $extensions[0];
         }
     }
     if (is_null($extension)) {
         throw new UploadStashFileException("extension is null");
     }
     return File::normalizeExtension($extension);
 }
 /**
  * Helper function that does various existence checks for a file.
  * The following checks are performed:
  * - The file exists
  * - Article with the same name as the file exists
  * - File exists with normalized extension
  * - The file looks like a thumbnail and the original exists
  *
  * @param $file File The File object to check
  * @return mixed False if the file does not exists, else an array
  */
 public static function getExistsWarning($file)
 {
     if ($file->exists()) {
         return array('warning' => 'exists', 'file' => $file);
     }
     if ($file->getTitle()->getArticleID()) {
         return array('warning' => 'page-exists', 'file' => $file);
     }
     if ($file->wasDeleted() && !$file->exists()) {
         return array('warning' => 'was-deleted', 'file' => $file);
     }
     if (strpos($file->getName(), '.') == false) {
         $partname = $file->getName();
         $extension = '';
     } else {
         $n = strrpos($file->getName(), '.');
         $extension = substr($file->getName(), $n + 1);
         $partname = substr($file->getName(), 0, $n);
     }
     $normalizedExtension = File::normalizeExtension($extension);
     if ($normalizedExtension != $extension) {
         // We're not using the normalized form of the extension.
         // Normal form is lowercase, using most common of alternate
         // extensions (eg 'jpg' rather than 'JPEG').
         //
         // Check for another file using the normalized form...
         $nt_lc = Title::makeTitle(NS_FILE, "{$partname}.{$normalizedExtension}");
         $file_lc = wfLocalFile($nt_lc);
         if ($file_lc->exists()) {
             return array('warning' => 'exists-normalized', 'file' => $file, 'normalizedFile' => $file_lc);
         }
     }
     // Check for files with the same name but a different extension
     $similarFiles = RepoGroup::singleton()->getLocalRepo()->findFilesByPrefix("{$partname}.", 1);
     if (count($similarFiles)) {
         return array('warning' => 'exists-normalized', 'file' => $file, 'normalizedFile' => $similarFiles[0]);
     }
     if (self::isThumbName($file->getName())) {
         # Check for filenames like 50px- or 180px-, these are mostly thumbnails
         $nt_thb = Title::newFromText(substr($partname, strpos($partname, '-') + 1) . '.' . $extension, NS_FILE);
         $file_thb = wfLocalFile($nt_thb);
         if ($file_thb->exists()) {
             return array('warning' => 'thumb', 'file' => $file, 'thumbFile' => $file_thb);
         } else {
             // File does not exist, but we just don't like the name
             return array('warning' => 'thumb-name', 'file' => $file, 'thumbFile' => $file_thb);
         }
     }
     foreach (self::getFilenamePrefixBlacklist() as $prefix) {
         if (substr($partname, 0, strlen($prefix)) == $prefix) {
             return array('warning' => 'bad-prefix', 'file' => $file, 'prefix' => $prefix);
         }
     }
     return false;
 }
Example #6
0
 /**
  * Find or guess extension -- ensuring that our extension matches our mime type.
  * Since these files are constructed from php tempnames they may not start off
  * with an extension.
  * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
  * uploads versus the desired filename. Maybe we can get that passed to us...
  * @param $path
  * @throws UploadStashFileException
  * @return string
  */
 public static function getExtensionForPath($path)
 {
     global $wgFileBlacklist;
     // Does this have an extension?
     $n = strrpos($path, '.');
     $extension = null;
     if ($n !== false) {
         $extension = $n ? substr($path, $n + 1) : '';
     } else {
         // If not, assume that it should be related to the mime type of the original file.
         $magic = MimeMagic::singleton();
         $mimeType = $magic->guessMimeType($path);
         $extensions = explode(' ', MimeMagic::singleton()->getExtensionsForType($mimeType));
         if (count($extensions)) {
             $extension = $extensions[0];
         }
     }
     if (is_null($extension)) {
         throw new UploadStashFileException("extension is null");
     }
     $extension = File::normalizeExtension($extension);
     if (in_array($extension, $wgFileBlacklist)) {
         // The file should already be checked for being evil.
         // However, if somehow we got here, we definitely
         // don't want to give it an extension of .php and
         // put it in a web accesible directory.
         return '';
     }
     return $extension;
 }
	function execute() {
		global $wgRequest, $wgContLanguageCode;

		if ( !$this->scalerAccessRanges ) {
			$this->htmlError( 403, 'inplace_access_disabled' );
			return false;
		}

		/**
		 * Run access checks against REMOTE_ADDR rather than wfGetIP(), since we're not
		 * giving access even to trusted proxies, only direct clients.
		 */
		$allowed = false;
		foreach ( $this->scalerAccessRanges as $range ) {
			if ( IP::isInRange( $_SERVER['REMOTE_ADDR'], $range ) ) {
				$allowed = true;
				break;
			}
		}

		if ( !$allowed ) {
			$this->htmlError( 403, 'inplace_access_denied' );
			return false;
		}

		if ( !$wgRequest->wasPosted() ) {
			echo $this->dtd();
?>
<html>
<head><title>inplace-scaler.php Test Interface</title></head>
<body>
<form method="post" action="inplace-scaler.php" enctype="multipart/form-data" >
<p>File: <input type="file" name="data" /></p>
<p>Width: <input type="text" name="width" /></p>
<p>Page: <input type="page" name="page" /></p>
<p><input type="submit" value="OK" /></p>
</form>
</body>
</html>
<?php
			return true;
		}

		$tempDir = $this->tmpDir . '/' . gmdate( self::$tempDirFormat );
		if ( !is_dir( $tempDir ) ) {
			if ( !wfMkdirParents( $tempDir, null, __METHOD__ ) ) {
				$this->htmlError( 500, 'inplace_scaler_no_temp' );
				return false;
			}
		}

		$name = $wgRequest->getFileName( 'data' );
		$srcTemp = $wgRequest->getFileTempname( 'data' );

		$params = $_REQUEST;
		unset( $params['file'] );
		if ( get_magic_quotes_gpc() ) {
			$params = array_map( 'stripslashes', $params );
		}

		$i = strrpos( $name, '.' );
		$ext = File::normalizeExtension( $i ? substr( $name, $i + 1 ) : '' );

		$magic = MimeMagic::singleton();
		$mime = $magic->guessTypesForExtension( $ext );

		$image = UnregisteredLocalFile::newFromPath( $srcTemp, $mime );

		$handler = $image->getHandler();
		if ( !$handler ) {
			$this->htmlError( 400, 'inplace_scaler_no_handler' );
			return false;
		}

		if ( !isset( $params['page'] ) ) {
			$params['page'] = 1;
		}
		$srcWidth = $image->getWidth( $params['page'] );
		$srcHeight = $image->getHeight( $params['page'] );
		if ( $srcWidth <= 0 || $srcHeight <= 0 ) {
			$this->htmlError( 400, 'inplace_scaler_invalid_image' );
			return false;
		}

		list( $dstExt, $dstMime ) = $handler->getThumbType( $ext, $mime );
		if ( preg_match( '/[ \\n;=]/', $name ) ) {
			$dstName = "thumb.$ext";
		} else {
			$dstName = $name;
		}
		if ( $dstExt != $ext ) {
			$dstName = "$dstName.$dstExt";
		}

		$dstTemp = tempnam( $tempDir, 'mwimg' );

		$thumb = $handler->doTransform( $image, $dstTemp, false, $params );
		if ( !$thumb || $thumb->isError()  ) {
			$error = $thumb ? $thumb->getHtmlMsg() : '';
			$this->htmlErrorReal( 500, 'inplace_scaler_failed', array(''), $error );
			unlink( $dstTemp );
			return false;
		}
		$stat = stat( $dstTemp );
		if ( !$stat  ) {
			$this->htmlError( 500, 'inplace_scaler_no_output' );
			return false;
		}

		if ( $stat['size'] == 0 ) {
			$this->htmlError( 500, 'inplace_scaler_no_output' );
			unlink( $dstTemp );
			return false;
		}

		wfDebug( __METHOD__.": transformation completed successfully, streaming output...\n" );
		header( "Content-Type: $dstMime" );
		header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( $dstName ) );
		readfile( $dstTemp );
		unlink( $dstTemp );
	}
	/**
	 * Delete files in the deleted directory if they are not referenced in the
	 * filearchive table. This needs to be done in the repo because it needs to
	 * interleave database locks with file operations, which is potentially a
	 * remote operation.
	 * @return FileRepoStatus
	 */
	function cleanupDeletedBatch( $storageKeys ) {
		$conn = $this->connect();
		$cont = $this->getZoneContainer( 'deleted' );
		$container = $this->get_container( $conn, $cont );

		$dbw = $this->getMasterDB();
		$status = $this->newGood();
		$storageKeys = array_unique( $storageKeys );
		foreach ( $storageKeys as $key ) {
			$hashPath = $this->getDeletedHashPath( $key );
			$rel = "$hashPath$key";
			$dbw->begin();
			$inuse = $dbw->selectField( 'filearchive', '1',
				array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
				__METHOD__, array( 'FOR UPDATE' ) );
			if ( !$inuse ) {
				$sha1 = self::getHashFromKey( $key );
				$ext = substr( $key, strcspn( $key, '.' ) + 1 );
				$ext = File::normalizeExtension( $ext );
				$inuse = $dbw->selectField( 'oldimage', '1',
					array( 'oi_sha1' => $sha1,
						'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
						$dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
					__METHOD__, array( 'FOR UPDATE' ) );
			}
			if ( !$inuse ) {
				wfDebug( __METHOD__ . ": deleting $key\n" );
				$this->swift_delete( $container, $rel );
			} else {
				wfDebug( __METHOD__ . ": $key still in use\n" );
				$status->successCount++;
			}
			$dbw->commit();
		}
		return $status;
	}
	function execute() {
		global $wgRequest;

		if ( !$wgRequest->wasPosted() ) {
			echo $this->dtd();
?>
<html>
<head><title>metadata.php Test interface</title>
<body>
<form method="post" action="metadata.php">
<p>Zone: <select name="zone" value="public">
<option>public</option>
<option>temp</option>
<option>deleted</option>
</select>
</p>
<p>Relative path: <input type="text" name="path"></p>
<p><input type="submit" value="OK" /></p>
</form>
</body></html>
<?php
			return true;
		}

		$zone = $wgRequest->getVal( 'zone' );
		$root = $this->getZoneRoot( $zone );
		if ( strval( $root ) == '' ) {
				$this->error( 400, 'webstore_invalid_zone', $zone );
				return false;
		}

		$rel = $wgRequest->getVal( 'path' );
		if ( !$this->validateFilename( $rel ) ) {
			$this->error( 400, 'webstore_path_invalid' );
			return false;
		}

		$fullPath = $root . '/' . $rel;

		$name = basename( $fullPath );
		$i = strrpos( $name, '.' );
		$ext = File::normalizeExtension( $i ? substr( $name, $i + 1 ) : '' );
		$magic = MimeMagic::singleton();
		$mime = $magic->guessTypesForExtension( $ext );
		$type = $magic->getMediaType( $fullPath, $mime);

		$stat = stat( $fullPath );
		if ( !$stat ) {
			$this->error( 400, 'webstore_metadata_not_found', $fullPath );
			return false;
		}

		$image = UnregisteredLocalFile::newFromPath( $fullPath, $mime );
		if ( !$image->getHandler() ) {
			$this->error( 400, 'webstore_no_handler' );
			return false;
		}
		$gis = $image->getImageSize();
		$handlerMeta = $image->getMetadata();
		$stat = stat( $fullPath );

		$metadata = array(
			'width' => $gis[0],
			'height' => $gis[1],
			'bits' => isset( $gis['bits'] ) ? $gis['bits'] : '',
			'type' => $type,
			'mime' => $mime,
			'metadata' => $handlerMeta,
			'size' => $stat['size'],
		);

		header( 'Content-Type: text/xml' );
		echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<response><status>success</status><metadata>\n";
		foreach ( $metadata as $field => $value ) {
			if ( is_bool( $value ) ) {
				$value = $value ? 1 : 0;
			}
			echo "<item name=\"$field\">" . htmlspecialchars( $value ) . "</item>\n";
		}
		echo "</metadata></response>\n";
	}