/** * 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) * @param $key String: optional, unique key for this file. Used for directory hashing when storing, otherwise not important * @throws UploadStashBadPathException * @throws UploadStashFileException * @throws UploadStashNotLoggedInException * @return UploadStashFile: file, or null on failure */ public function stashFile($path, $sourceType = null, $key = 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 = File::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 some users of this function might expect to supply the key instead of using the generated one. if (is_null($key)) { // 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'); } } throw new UploadStashFileException("error storing file in '{$path}': " . implode('; ', $error)); } $stashPath = $storeStatus->value; // 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(); // select happens on the master so this can all be in a transaction, which // avoids a race condition that's likely with multiple people uploading from the same // set of files $dbw->begin(); // first, check to see if it's already there. $row = $dbw->selectRow('uploadstash', 'us_user, us_timestamp', array('us_key' => $key), __METHOD__); // The current user can't have this key if: // - the key is owned by someone else and // - the age of the key is less than REPO_AGE if (is_object($row)) { if ($row->us_user != $this->userId && $row->wfTimestamp(TS_UNIX, $row->us_timestamp) > time() - UploadStash::REPO_AGE * 3600) { $dbw->rollback(); throw new UploadStashWrongOwnerException("Attempting to upload a duplicate of a file that someone else has stashed"); } } $this->fileMetadata[$key] = array('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'); // if a row exists but previous checks on it passed, let the current user take over this key. $dbw->replace('uploadstash', 'us_key', $this->fileMetadata[$key], __METHOD__); $dbw->commit(); // 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); }
/** * 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); }