/** * Prepares the saving process by generating the hash and the place where the file is stored. * * Called when this component receives an HTTP POST request to * /$a/$b/$c/$file. * The request body should contain a JSON object representing the file's * attributes. */ public function addFile($callName, $input, $params = array()) { $fileObject = $input; $fileContent = $fileObject->getBody(true); $fileObject->setBody(null); $fileObject->setHash(sha1($fileContent)); $filePath = FSFile::generateFilePath(FSFile::getBaseDir(), $fileObject->getHash()); $fileObject->setAddress(FSFile::getBaseDir() . '/' . $fileObject->getHash()); if (!file_exists($this->config['DIR']['files'] . '/' . $filePath)) { FSFile::generatepath($this->config['DIR']['files'] . '/' . dirname($filePath)); // writes the file to filesystem $file = fopen($this->config['DIR']['files'] . '/' . $filePath, 'w'); if ($file) { fwrite($file, $fileContent); fclose($file); $fileObject->setStatus(201); } else { $fileObject->addMessage("Datei konnte nicht im Dateisystem angelegt werden."); $fileObject->setStatus(409); Logger::Log('POST postFile failed', LogLevel::ERROR); return Model::isCreated($fileObject); } } // resets the file content $fileObject->setBody(null); // generate new file address, file size and file hash $fileObject->setAddress($filePath); $fileObject->setFileSize(filesize($this->config['DIR']['files'] . '/' . $filePath)); $fileObject->setHash(sha1_file($this->config['DIR']['files'] . '/' . $filePath)); $fileObject->setMimeType(MimeReader::get_mime($this->config['DIR']['files'] . '/' . $filePath)); return Model::isCreated($fileObject); }
public function __construct($path) { parent::__construct($path); if (self::$pathsCollect === null) { self::$pathsCollect = array(); register_shutdown_function(array(__CLASS__, 'purgeAllOnShutdown')); } }
protected function getFileDuplicate($filepath) { $duplicates = RepoGroup::singleton()->findBySha1(FSFile::getSha1Base36FromPath($filepath)); if (count($duplicates) > 0) { return $duplicates[0]; } return null; }
/** * Helper function -- given a file on the filesystem, find matching * content in the db (and associated articles) and remove them. * * @param string $filePath Path to file on the filesystem * * @return bool */ public function deleteFileByContent($filePath) { $hash = FSFile::getSha1Base36FromPath($filePath); $dupes = RepoGroup::singleton()->findBySha1($hash); $success = true; foreach ($dupes as $dupe) { $success &= $this->deleteFileByTitle($dupe->getTitle()); } return $success; }
/** * A verification routine suitable for partial files * * Runs the blacklist checks, but not any checks that may * assume the entire file is present. * * @return Mixed true for valid or array with error message key. */ protected function verifyPartialFile() { global $wgAllowJavaUploads, $wgDisableUploadScriptChecks; wfProfileIn(__METHOD__); # getTitle() sets some internal parameters like $this->mFinalExtension $this->getTitle(); $this->mFileProps = FSFile::getPropsFromPath($this->mTempPath, $this->mFinalExtension); # check mime type, if desired $mime = $this->mFileProps['file-mime']; $status = $this->verifyMimeType($mime); if ($status !== true) { wfProfileOut(__METHOD__); return $status; } # check for htmlish code and javascript if (!$wgDisableUploadScriptChecks) { if (self::detectScript($this->mTempPath, $mime, $this->mFinalExtension)) { wfProfileOut(__METHOD__); return array('uploadscripted'); } if ($this->mFinalExtension == 'svg' || $mime == 'image/svg+xml') { $svgStatus = $this->detectScriptInSvg($this->mTempPath); if ($svgStatus !== false) { wfProfileOut(__METHOD__); return $svgStatus; } } } # Check for Java applets, which if uploaded can bypass cross-site # restrictions. if (!$wgAllowJavaUploads) { $this->mJavaDetected = false; $zipStatus = ZipDirectoryReader::read($this->mTempPath, array($this, 'zipEntryCallback')); if (!$zipStatus->isOK()) { $errors = $zipStatus->getErrorsArray(); $error = reset($errors); if ($error[0] !== 'zip-wrong-format') { wfProfileOut(__METHOD__); return $error; } } if ($this->mJavaDetected) { wfProfileOut(__METHOD__); return array('uploadjava'); } } # Scan the uploaded file for viruses $virus = $this->detectVirus($this->mTempPath); if ($virus) { wfProfileOut(__METHOD__); return array('uploadvirus', $virus); } wfProfileOut(__METHOD__); return true; }
/** * Get properties of a file with a given virtual URL * The virtual URL must refer to this repo */ function getFileProps($virtualUrl) { $path = $this->resolveVirtualUrl($virtualUrl); return FSFile::getPropsFromPath($path); }
/** * Upload a file and record it in the DB * @param string $srcPath Source storage path, virtual URL, or filesystem path * @param string $comment Upload description * @param string $pageText Text to use for the new description page, * if a new description page is created * @param int|bool $flags Flags for publish() * @param array|bool $props File properties, if known. This can be used to * reduce the upload time when uploading virtual URLs for which the file * info is already known * @param string|bool $timestamp Timestamp for img_timestamp, or false to use the * current time * @param User|null $user User object or null to use $wgUser * @param string[] $tags Change tags to add to the log entry and page revision. * (This doesn't check $user's permissions.) * @return FileRepoStatus On success, the value member contains the * archive name, or an empty string if it was a new file. */ function upload($srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null, $tags = array()) { global $wgContLang; if ($this->getRepo()->getReadOnlyReason() !== false) { return $this->readOnlyFatalStatus(); } if (!$props) { if ($this->repo->isVirtualUrl($srcPath) || FileBackend::isStoragePath($srcPath)) { $props = $this->repo->getFileProps($srcPath); } else { $props = FSFile::getPropsFromPath($srcPath); } } $options = array(); $handler = MediaHandler::getHandler($props['mime']); if ($handler) { $options['headers'] = $handler->getStreamHeaders($props['metadata']); } else { $options['headers'] = array(); } // Trim spaces on user supplied text $comment = trim($comment); // Truncate nicely or the DB will do it for us // non-nicely (dangling multi-byte chars, non-truncated version in cache). $comment = $wgContLang->truncate($comment, 255); $this->lock(); // begin $status = $this->publish($srcPath, $flags, $options); if ($status->successCount >= 2) { // There will be a copy+(one of move,copy,store). // The first succeeding does not commit us to updating the DB // since it simply copied the current version to a timestamped file name. // It is only *preferable* to avoid leaving such files orphaned. // Once the second operation goes through, then the current version was // updated and we must therefore update the DB too. $oldver = $status->value; if (!$this->recordUpload2($oldver, $comment, $pageText, $props, $timestamp, $user, $tags)) { $status->fatal('filenotfound', $srcPath); } } $this->unlock(); // done return $status; }
/** * Load metadata from the file itself */ function loadFromFile() { $this->setProps(FSFile::getPropsFromPath($this->getPath())); }
/** * @see FileBackend::getFileProps() * @return Array */ public final function getFileProps(array $params) { wfProfileIn(__METHOD__); wfProfileIn(__METHOD__ . '-' . $this->name); $fsFile = $this->getLocalReference($params); $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps(); wfProfileOut(__METHOD__ . '-' . $this->name); wfProfileOut(__METHOD__); return $props; }
public final function getFileProps(array $params) { $ps = $this->scopedProfileSection(__METHOD__ . "-{$this->name}"); $fsFile = $this->getLocalReference($params); $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps(); return $props; }
/** * @param File|FSFile $file * @param string $path Unused * @param bool|array $metadata * @return array */ function getImageSize($file, $path, $metadata = false) { if ($metadata === false && $file instanceof File) { $metadata = $file->getMetadata(); } $metadata = $this->unpackMetadata($metadata); if (isset($metadata['width']) && isset($metadata['height'])) { return [$metadata['width'], $metadata['height'], 'SVG', "width=\"{$metadata['width']}\" height=\"{$metadata['height']}\""]; } else { // error return [0, 0, 'SVG', "width=\"0\" height=\"0\""]; } }
/** * Stash a file in a temp directory and record that we did this in the database, along with other metadata. * * @param $path String: path to file you want stashed * @param $sourceType String: the type of upload that generated this file (currently, I believe, 'file' or null) * @throws UploadStashBadPathException * @throws UploadStashFileException * @throws UploadStashNotLoggedInException * @return UploadStashFile: file, or null on failure */ public function stashFile($path, $sourceType = null) { if (!file_exists($path)) { wfDebug(__METHOD__ . " tried to stash file at '{$path}', but it doesn't exist\n"); throw new UploadStashBadPathException("path doesn't exist"); } $fileProps = FSFile::getPropsFromPath($path); wfDebug(__METHOD__ . " stashing file at '{$path}'\n"); // we will be initializing from some tmpnam files that don't have extensions. // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. $extension = self::getExtensionForPath($path); if (!preg_match("/\\.\\Q{$extension}\\E\$/", $path)) { $pathWithGoodExtension = "{$path}.{$extension}"; if (!rename($path, $pathWithGoodExtension)) { throw new UploadStashFileException("couldn't rename {$path} to have a better extension at {$pathWithGoodExtension}"); } $path = $pathWithGoodExtension; } // If no key was supplied, make one. a mysql insertid would be totally reasonable here, except // that for historical reasons, the key is this random thing instead. At least it's not guessable. // // some things that when combined will make a suitably unique key. // see: http://www.jwz.org/doc/mid.html list($usec, $sec) = explode(' ', microtime()); $usec = substr($usec, 2); $key = wfBaseConvert($sec . $usec, 10, 36) . '.' . wfBaseConvert(mt_rand(), 10, 36) . '.' . $this->userId . '.' . $extension; $this->fileProps[$key] = $fileProps; if (!preg_match(self::KEY_FORMAT_REGEX, $key)) { throw new UploadStashBadPathException("key '{$key}' is not in a proper format"); } wfDebug(__METHOD__ . " key for '{$path}': {$key}\n"); // if not already in a temporary area, put it there $storeStatus = $this->repo->storeTemp(basename($path), $path); if (!$storeStatus->isOK()) { // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings. // This is a bit lame, as we may have more info in the $storeStatus and we're throwing it away, but to fix it means // redesigning API errors significantly. // $storeStatus->value just contains the virtual URL (if anything) which is probably useless to the caller $error = $storeStatus->getErrorsArray(); $error = reset($error); if (!count($error)) { $error = $storeStatus->getWarningsArray(); $error = reset($error); if (!count($error)) { $error = array('unknown', 'no error recorded'); } } // at this point, $error should contain the single "most important" error, plus any parameters. $errorMsg = array_shift($error); throw new UploadStashFileException("Error storing file in '{$path}': " . wfMessage($errorMsg, $error)->text()); } $stashPath = $storeStatus->value; // we have renamed the file so we have to cleanup once done unlink($path); // fetch the current user ID if (!$this->isLoggedIn) { throw new UploadStashNotLoggedInException(__METHOD__ . ' No user is logged in, files must belong to users'); } // insert the file metadata into the db. wfDebug(__METHOD__ . " inserting {$stashPath} under {$key}\n"); $dbw = $this->repo->getMasterDb(); $this->fileMetadata[$key] = array('us_id' => $dbw->nextSequenceValue('uploadstash_us_id_seq'), 'us_user' => $this->userId, 'us_key' => $key, 'us_orig_path' => $path, 'us_path' => $stashPath, 'us_size' => $fileProps['size'], 'us_sha1' => $fileProps['sha1'], 'us_mime' => $fileProps['mime'], 'us_media_type' => $fileProps['media_type'], 'us_image_width' => $fileProps['width'], 'us_image_height' => $fileProps['height'], 'us_image_bits' => $fileProps['bits'], 'us_source_type' => $sourceType, 'us_timestamp' => $dbw->timestamp(), 'us_status' => 'finished'); $dbw->insert('uploadstash', $this->fileMetadata[$key], __METHOD__); // store the insertid in the class variable so immediate retrieval (possibly laggy) isn't necesary. $this->fileMetadata[$key]['us_id'] = $dbw->insertId(); # create the UploadStashFile object for this file. $this->initFile($key); return $this->getFile($key); }
/** * Upload a file and record it in the DB * @param string $srcPath Source storage path, virtual URL, or filesystem path * @param string $comment Upload description * @param string $pageText Text to use for the new description page, * if a new description page is created * @param int|bool $flags Flags for publish() * @param array|bool $props File properties, if known. This can be used to * reduce the upload time when uploading virtual URLs for which the file * info is already known * @param string|bool $timestamp Timestamp for img_timestamp, or false to use the * current time * @param User|null $user User object or null to use $wgUser * * @return FileRepoStatus object. On success, the value member contains the * archive name, or an empty string if it was a new file. */ function upload($srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null) { global $wgContLang; if ($this->getRepo()->getReadOnlyReason() !== false) { return $this->readOnlyFatalStatus(); } if (!$props) { wfProfileIn(__METHOD__ . '-getProps'); if ($this->repo->isVirtualUrl($srcPath) || FileBackend::isStoragePath($srcPath)) { $props = $this->repo->getFileProps($srcPath); } else { $props = FSFile::getPropsFromPath($srcPath); } wfProfileOut(__METHOD__ . '-getProps'); } $options = array(); $handler = MediaHandler::getHandler($props['mime']); if ($handler) { $options['headers'] = $handler->getStreamHeaders($props['metadata']); } else { $options['headers'] = array(); } // Trim spaces on user supplied text $comment = trim($comment); // truncate nicely or the DB will do it for us // non-nicely (dangling multi-byte chars, non-truncated version in cache). $comment = $wgContLang->truncate($comment, 255); $this->lock(); // begin $status = $this->publish($srcPath, $flags, $options); if ($status->successCount > 0) { # Essentially we are displacing any existing current file and saving # a new current file at the old location. If just the first succeeded, # we still need to displace the current DB entry and put in a new one. if (!$this->recordUpload2($status->value, $comment, $pageText, $props, $timestamp, $user)) { $status->fatal('filenotfound', $srcPath); } } $this->unlock(); // done return $status; }
/** * Check for non fatal problems with the file * * @return Array of warnings */ public function checkWarnings() { global $wgLang; $warnings = array(); $localFile = $this->getLocalFile(); $filename = $localFile->getName(); /** * Check whether the resulting filename is different from the desired one, * but ignore things like ucfirst() and spaces/underscore things */ $comparableName = str_replace(' ', '_', $this->mDesiredDestName); $comparableName = Title::capitalize($comparableName, NS_FILE); if ($this->mDesiredDestName != $filename && $comparableName != $filename) { $warnings['badfilename'] = $filename; } // Check whether the file extension is on the unwanted list global $wgCheckFileExtensions, $wgFileExtensions; if ($wgCheckFileExtensions) { if (!$this->checkFileExtension($this->mFinalExtension, $wgFileExtensions)) { $warnings['filetype-unwanted-type'] = array($this->mFinalExtension, $wgLang->commaList($wgFileExtensions), count($wgFileExtensions)); } } global $wgUploadSizeWarning; if ($wgUploadSizeWarning && $this->mFileSize > $wgUploadSizeWarning) { $warnings['large-file'] = $wgUploadSizeWarning; } if ($this->mFileSize == 0) { $warnings['emptyfile'] = true; } $exists = self::getExistsWarning($localFile); if ($exists !== false) { $warnings['exists'] = $exists; } // Check dupes against existing files $hash = FSFile::getSha1Base36FromPath($this->mTempPath); $dupes = RepoGroup::singleton()->findBySha1($hash); $title = $this->getTitle(); // Remove all matches against self foreach ($dupes as $key => $dupe) { if ($title->equals($dupe->getTitle())) { unset($dupes[$key]); } } if ($dupes) { $warnings['duplicate'] = $dupes; } // Check dupes against archives $archivedImage = new ArchivedFile(null, 0, "{$hash}.{$this->mFinalExtension}"); if ($archivedImage->getID() > 0) { $warnings['duplicate-archive'] = $archivedImage->getName(); } return $warnings; }
/** * Get the base 36 SHA1 of the file * @return string */ public function getTempFileSha1Base36() { return FSFile::getSha1Base36FromPath($this->mTempPath); }
/** * @param string $fileName * @return array */ function getFileProps($fileName) { if (FileRepo::isVirtualUrl($fileName)) { list($repoName, , ) = $this->splitVirtualUrl($fileName); if ($repoName === '') { $repoName = 'local'; } $repo = $this->getRepo($repoName); return $repo->getFileProps($fileName); } else { return FSFile::getPropsFromPath($fileName); } }
/** * Move or copy a file to a specified location. Returns a FileRepoStatus * object with the archive name in the "value" member on success. * * The archive name should be passed through to recordUpload for database * registration. * * @param string|FSFile $src Local filesystem path or virtual URL to the source image * @param string $dstRel Target relative path * @param int $flags A bitwise combination of: * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy * @param array $options Optional additional parameters * @return FileRepoStatus On success, the value member contains the * archive name, or an empty string if it was a new file. */ function publishTo($src, $dstRel, $flags = 0, array $options = []) { $srcPath = $src instanceof FSFile ? $src->getPath() : $src; $repo = $this->getRepo(); if ($repo->getReadOnlyReason() !== false) { return $this->readOnlyFatalStatus(); } $this->lock(); // begin $archiveName = wfTimestamp(TS_MW) . '!' . $this->getName(); $archiveRel = 'archive/' . $this->getHashPath() . $archiveName; if ($repo->hasSha1Storage()) { $sha1 = $repo->isVirtualUrl($srcPath) ? $repo->getFileSha1($srcPath) : FSFile::getSha1Base36FromPath($srcPath); $dst = $repo->getBackend()->getPathForSHA1($sha1); $status = $repo->quickImport($src, $dst); if ($flags & File::DELETE_SOURCE) { unlink($srcPath); } if ($this->exists()) { $status->value = $archiveName; } } else { $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0; $status = $repo->publish($srcPath, $dstRel, $archiveRel, $flags, $options); if ($status->value == 'new') { $status->value = ''; } else { $status->value = $archiveName; } } $this->unlock(); // done return $status; }
/** * Empty place holder props for non-existing files * * Resulting array fields include: * - fileExists * - size (filesize in bytes) * - mime (as major/minor) * - media_type (value to be used with the MEDIATYPE_xxx constants) * - metadata (handler specific) * - sha1 (in base 36) * - width * - height * - bits (bitrate) * - file-mime * - major_mime * - minor_mime * * @return array * @since 1.28 */ public function newPlaceholderProps() { return FSFile::placeholderProps() + ['metadata' => '', 'width' => 0, 'height' => 0, 'bits' => 0, 'media_type' => MEDIATYPE_UNKNOWN]; }
private function doTestStore($op) { $backendName = $this->backendClass(); $source = $op['src']; $dest = $op['dst']; $this->prepare(array('dir' => dirname($dest))); file_put_contents($source, "Unit test file"); if (isset($op['overwrite']) || isset($op['overwriteSame'])) { $this->backend->store($op); } $status = $this->backend->doOperation($op); $this->assertGoodStatus($status, "Store from {$source} to {$dest} succeeded without warnings ({$backendName})."); $this->assertEquals(true, $status->isOK(), "Store from {$source} to {$dest} succeeded ({$backendName})."); $this->assertEquals(array(0 => true), $status->success, "Store from {$source} to {$dest} has proper 'success' field in Status ({$backendName})."); $this->assertEquals(true, file_exists($source), "Source file {$source} still exists ({$backendName})."); $this->assertEquals(true, $this->backend->fileExists(array('src' => $dest)), "Destination file {$dest} exists ({$backendName})."); $this->assertEquals(filesize($source), $this->backend->getFileSize(array('src' => $dest)), "Destination file {$dest} has correct size ({$backendName})."); $props1 = FSFile::getPropsFromPath($source); $props2 = $this->backend->getFileProps(array('src' => $dest)); $this->assertEquals($props1, $props2, "Source and destination have the same props ({$backendName})."); $this->assertBackendPathsConsistent(array($dest)); }
# Check existence $image = wfLocalFile($title); if ($image->exists()) { if (isset($options['overwrite'])) { echo "{$base} exists, overwriting..."; $svar = 'overwritten'; } else { echo "{$base} exists, skipping\n"; $skipped++; continue; } } else { if (isset($options['skip-dupes'])) { $repo = $image->getRepo(); # XXX: we end up calculating this again when actually uploading. that sucks. $sha1 = FSFile::getSha1Base36FromPath($file); $dupes = $repo->findBySha1($sha1); if ($dupes) { echo "{$base} already exists as " . $dupes[0]->getName() . ", skipping\n"; $skipped++; continue; } } echo "Importing {$base}..."; $svar = 'added'; } if (isset($options['source-wiki-url'])) { /* find comment text directly from source wiki, through MW's API */ $real_comment = getFileCommentFromSourceWiki($options['source-wiki-url'], $base); if ($real_comment === false) { $commentText = $comment;
/** * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case * encoding, zero padded to 31 digits. * * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36 * fairly neatly. * * @param $path string * * @return bool|string False on failure * @deprecated since 1.19 */ static function sha1Base36($path) { wfDeprecated(__METHOD__, '1.19'); $fsFile = new FSFile($path); return $fsFile->getSha1Base36(); }
} else { $commentText = file_get_contents($f); if (!$commentText) { echo " Failed to load comment file {$f}, using default comment. "; } } } if (!$commentText) { $commentText = $comment; } } # Import the file if (isset($options['dry'])) { echo " publishing {$file} by '" . $wgUser->getName() . "', comment '{$commentText}'... "; } else { $props = FSFile::getPropsFromPath($file); $flags = 0; $publishOptions = array(); $handler = MediaHandler::getHandler($props['mime']); if ($handler) { $publishOptions['headers'] = $handler->getStreamHeaders($props['metadata']); } else { $publishOptions['headers'] = array(); } $archive = $image->publish($file, $flags, $publishOptions); if (!$archive->isGood()) { echo "failed. (" . $archive->getWikiText() . ")\n"; $failed++; continue; } }