示例#1
0
 /**
  * @dataProvider provider_testTimestampedUID
  */
 public function testTimestampedUID($method, $digitlen, $bits, $tbits, $hostbits)
 {
     $id = call_user_func(array('UIDGenerator', $method));
     $this->assertEquals(true, ctype_digit($id), "UID made of digit characters");
     $this->assertLessThanOrEqual($digitlen, strlen($id), "UID has the right number of digits");
     $this->assertLessThanOrEqual($bits, strlen(wfBaseConvert($id, 10, 2)), "UID has the right number of bits");
     $ids = array();
     for ($i = 0; $i < 300; $i++) {
         $ids[] = call_user_func(array('UIDGenerator', $method));
     }
     $lastId = array_shift($ids);
     if ($hostbits) {
         $lastHost = substr(wfBaseConvert($lastId, 10, 2, $bits), -$hostbits);
     }
     $this->assertArrayEquals(array_unique($ids), $ids, "All generated IDs are unique.");
     foreach ($ids as $id) {
         $id_bin = wfBaseConvert($id, 10, 2);
         $lastId_bin = wfBaseConvert($lastId, 10, 2);
         $this->assertGreaterThanOrEqual(substr($id_bin, 0, $tbits), substr($lastId_bin, 0, $tbits), "New ID timestamp ({$id_bin}) >= prior one ({$lastId_bin}).");
         if ($hostbits) {
             $this->assertEquals(substr($id_bin, 0, -$hostbits), substr($lastId_bin, 0, -$hostbits), "Host ID of ({$id_bin}) is same as prior one ({$lastId_bin}).");
         }
         $lastId = $id;
     }
 }
示例#2
0
 /**
  * Get a statistically unique ID string
  *
  * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars>
  */
 public final function getTimestampedUUID()
 {
     $s = '';
     for ($i = 0; $i < 5; $i++) {
         $s .= mt_rand(0, 2147483647);
     }
     $s = wfBaseConvert(sha1($s), 16, 36, 31);
     return substr(wfBaseConvert(wfTimestamp(TS_MW), 10, 36, 9) . $s, 0, 31);
 }
 public static function uuidConversionProvider()
 {
     $dbr = wfGetDB(DB_SLAVE);
     // sample uuid from UIDGenerator::newTimestampedUID128()
     $numeric_128 = '6709199728898751234959525538795913762';
     $hex_128 = wfBaseConvert($numeric_128, 10, 16, 32);
     $bin_128 = new UUIDBlob(pack('H*', $hex_128));
     $pretty_128 = wfBaseConvert($numeric_128, 10, 36);
     // Conversion from 128 bit to 88 bit takes the left
     // most 88 bits.
     $bits_88 = substr(wfBaseConvert($numeric_128, 10, 2, 128), 0, 88);
     $numeric_88 = wfBaseConvert($bits_88, 2, 10);
     $hex_88 = wfBaseConvert($numeric_88, 10, 16, 22);
     $bin_88 = new UUIDBlob(pack('H*', $hex_88));
     $pretty_88 = wfBaseConvert($numeric_88, 10, 36);
     return array(array('128 bit hex input must be truncated to 88bit output', $hex_128, $bin_88, $hex_88, $pretty_88), array('88 bit binary input', $bin_88, $bin_88, $hex_88, $pretty_88), array('88 bit numeric input', $numeric_88, $bin_88, $hex_88, $pretty_88), array('88 bit hex input', $hex_88, $bin_88, $hex_88, $pretty_88), array('88 bit pretty input', $pretty_88, $bin_88, $hex_88, $pretty_88));
 }
 /**
  * @see ExternalStoreMedium::store()
  */
 public function store($backend, $data)
 {
     $be = FileBackendGroup::singleton()->get($backend);
     if ($be instanceof FileBackend) {
         // Get three random base 36 characters to act as shard directories
         $rand = wfBaseConvert(mt_rand(0, 46655), 10, 36, 3);
         // Make sure ID is roughly lexicographically increasing for performance
         $id = str_pad(UIDGenerator::newTimestampedUID128(32), 26, '0', STR_PAD_LEFT);
         // Segregate items by wiki ID for the sake of bookkeeping
         $wiki = isset($this->params['wiki']) ? $this->params['wiki'] : wfWikiID();
         $url = $be->getContainerStoragePath('data') . '/' . rawurlencode($wiki) . "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}";
         $be->prepare(array('dir' => dirname($url), 'noAccess' => 1, 'noListing' => 1));
         if ($be->create(array('dst' => $url, 'content' => $data))->isOK()) {
             return $url;
         }
     }
     return false;
 }
示例#5
0
 /**
  * Construct a new instance from configuration.
  * 
  * $config paramaters include:
  *     'lockServers'  : Associative array of server names to configuration.
  *                      Configuration is an associative array that includes:
  *                      'host'    - IP address/hostname
  *                      'port'    - TCP port
  *                      'authKey' - Secret string the lock server uses
  *     'srvsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
  *                      each having an odd-numbered list of server names (peers) as values.
  *     'connTimeout'  : Lock server connection attempt timeout. [optional]
  *
  * @param Array $config 
  */
 public function __construct(array $config)
 {
     $this->lockServers = $config['lockServers'];
     // Sanitize srvsByBucket config to prevent PHP errors
     $this->srvsByBucket = array_filter($config['srvsByBucket'], 'is_array');
     $this->srvsByBucket = array_values($this->srvsByBucket);
     // consecutive
     if (isset($config['connTimeout'])) {
         $this->connTimeout = $config['connTimeout'];
     } else {
         $this->connTimeout = 3;
         // use some sane amount
     }
     $this->session = '';
     for ($i = 0; $i < 5; $i++) {
         $this->session .= mt_rand(0, 2147483647);
     }
     $this->session = wfBaseConvert(sha1($this->session), 16, 36, 31);
 }
示例#6
0
 /**
  * Hash data as a session ID
  *
  * Generally this will only be used when self::persistsSessionId() is false and
  * the provider has to base the session ID on the verified user's identity
  * or other static data.
  *
  * @param string $data
  * @param string|null $key Defaults to $this->config->get( 'SecretKey' )
  * @return string
  */
 protected final function hashToSessionId($data, $key = null)
 {
     if (!is_string($data)) {
         throw new \InvalidArgumentException('$data must be a string, ' . gettype($data) . ' was passed');
     }
     if ($key !== null && !is_string($key)) {
         throw new \InvalidArgumentException('$key must be a string or null, ' . gettype($key) . ' was passed');
     }
     $hash = \MWCryptHash::hmac("{$this}\n{$data}", $key ?: $this->config->get('SecretKey'), false);
     if (strlen($hash) < 32) {
         // Should never happen, even md5 is 128 bits
         // @codeCoverageIgnoreStart
         throw new \UnexpectedValueException('Hash fuction returned less than 128 bits');
         // @codeCoverageIgnoreEnd
     }
     if (strlen($hash) >= 40) {
         $hash = wfBaseConvert($hash, 16, 32, 32);
     }
     return substr($hash, -32);
 }
 /**
  * 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);
 }
示例#8
0
 /**
  * @return bool|string
  */
 function getSha1()
 {
     if ($this->sha1base36) {
         return wfBaseConvert($this->sha1base36, 36, 16);
     }
     return false;
 }
 /**
  * Get result information for an image revision
  *
  * @param File $file
  * @param array $prop Array of properties to get (in the keys)
  * @param ApiResult $result
  * @param array $thumbParams Containing 'width' and 'height' items, or null
  * @param array|bool|string $opts Options for data fetching.
  *   This is an array consisting of the keys:
  *    'version': The metadata version for the metadata option
  *    'language': The language for extmetadata property
  *    'multilang': Return all translations in extmetadata property
  *    'revdelUser': User to use when checking whether to show revision-deleted fields.
  * @return array Result array
  */
 static function getInfo($file, $prop, $result, $thumbParams = null, $opts = false)
 {
     global $wgContLang;
     $anyHidden = false;
     if (!$opts || is_string($opts)) {
         $opts = array('version' => $opts ?: 'latest', 'language' => $wgContLang, 'multilang' => false, 'extmetadatafilter' => array(), 'revdelUser' => null);
     }
     $version = $opts['version'];
     $vals = array(ApiResult::META_TYPE => 'assoc');
     // Timestamp is shown even if the file is revdelete'd in interface
     // so do same here.
     if (isset($prop['timestamp'])) {
         $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $file->getTimestamp());
     }
     // Handle external callers who don't pass revdelUser
     if (isset($opts['revdelUser']) && $opts['revdelUser']) {
         $revdelUser = $opts['revdelUser'];
         $canShowField = function ($field) use($file, $revdelUser) {
             return $file->userCan($field, $revdelUser);
         };
     } else {
         $canShowField = function ($field) use($file) {
             return !$file->isDeleted($field);
         };
     }
     $user = isset($prop['user']);
     $userid = isset($prop['userid']);
     if ($user || $userid) {
         if ($file->isDeleted(File::DELETED_USER)) {
             $vals['userhidden'] = true;
             $anyHidden = true;
         }
         if ($canShowField(File::DELETED_USER)) {
             if ($user) {
                 $vals['user'] = $file->getUser();
             }
             if ($userid) {
                 $vals['userid'] = $file->getUser('id');
             }
             if (!$file->getUser('id')) {
                 $vals['anon'] = true;
             }
         }
     }
     // This is shown even if the file is revdelete'd in interface
     // so do same here.
     if (isset($prop['size']) || isset($prop['dimensions'])) {
         $vals['size'] = intval($file->getSize());
         $vals['width'] = intval($file->getWidth());
         $vals['height'] = intval($file->getHeight());
         $pageCount = $file->pageCount();
         if ($pageCount !== false) {
             $vals['pagecount'] = $pageCount;
         }
         // length as in how many seconds long a video is.
         $length = $file->getLength();
         if ($length) {
             // Call it duration, because "length" can be ambiguous.
             $vals['duration'] = (double) $length;
         }
     }
     $pcomment = isset($prop['parsedcomment']);
     $comment = isset($prop['comment']);
     if ($pcomment || $comment) {
         if ($file->isDeleted(File::DELETED_COMMENT)) {
             $vals['commenthidden'] = true;
             $anyHidden = true;
         }
         if ($canShowField(File::DELETED_COMMENT)) {
             if ($pcomment) {
                 $vals['parsedcomment'] = Linker::formatComment($file->getDescription(File::RAW), $file->getTitle());
             }
             if ($comment) {
                 $vals['comment'] = $file->getDescription(File::RAW);
             }
         }
     }
     $canonicaltitle = isset($prop['canonicaltitle']);
     $url = isset($prop['url']);
     $sha1 = isset($prop['sha1']);
     $meta = isset($prop['metadata']);
     $extmetadata = isset($prop['extmetadata']);
     $commonmeta = isset($prop['commonmetadata']);
     $mime = isset($prop['mime']);
     $mediatype = isset($prop['mediatype']);
     $archive = isset($prop['archivename']);
     $bitdepth = isset($prop['bitdepth']);
     $uploadwarning = isset($prop['uploadwarning']);
     if ($uploadwarning) {
         $vals['html'] = SpecialUpload::getExistsWarning(UploadBase::getExistsWarning($file));
     }
     if ($file->isDeleted(File::DELETED_FILE)) {
         $vals['filehidden'] = true;
         $anyHidden = true;
     }
     if ($anyHidden && $file->isDeleted(File::DELETED_RESTRICTED)) {
         $vals['suppressed'] = true;
     }
     if (!$canShowField(File::DELETED_FILE)) {
         //Early return, tidier than indenting all following things one level
         return $vals;
     }
     if ($canonicaltitle) {
         $vals['canonicaltitle'] = $file->getTitle()->getPrefixedText();
     }
     if ($url) {
         if (!is_null($thumbParams)) {
             $mto = $file->transform($thumbParams);
             self::$transformCount++;
             if ($mto && !$mto->isError()) {
                 $vals['thumburl'] = wfExpandUrl($mto->getUrl(), PROTO_CURRENT);
                 // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted
                 // thumbnail sizes for the thumbnail actual size
                 if ($mto->getUrl() !== $file->getUrl()) {
                     $vals['thumbwidth'] = intval($mto->getWidth());
                     $vals['thumbheight'] = intval($mto->getHeight());
                 } else {
                     $vals['thumbwidth'] = intval($file->getWidth());
                     $vals['thumbheight'] = intval($file->getHeight());
                 }
                 if (isset($prop['thumbmime']) && $file->getHandler()) {
                     list(, $mime) = $file->getHandler()->getThumbType($mto->getExtension(), $file->getMimeType(), $thumbParams);
                     $vals['thumbmime'] = $mime;
                 }
             } elseif ($mto && $mto->isError()) {
                 $vals['thumberror'] = $mto->toText();
             }
         }
         $vals['url'] = wfExpandUrl($file->getFullURL(), PROTO_CURRENT);
         $vals['descriptionurl'] = wfExpandUrl($file->getDescriptionUrl(), PROTO_CURRENT);
     }
     if ($sha1) {
         $vals['sha1'] = wfBaseConvert($file->getSha1(), 36, 16, 40);
     }
     if ($meta) {
         wfSuppressWarnings();
         $metadata = unserialize($file->getMetadata());
         wfRestoreWarnings();
         if ($metadata && $version !== 'latest') {
             $metadata = $file->convertMetadataVersion($metadata, $version);
         }
         $vals['metadata'] = $metadata ? self::processMetaData($metadata, $result) : null;
     }
     if ($commonmeta) {
         $metaArray = $file->getCommonMetaArray();
         $vals['commonmetadata'] = $metaArray ? self::processMetaData($metaArray, $result) : array();
     }
     if ($extmetadata) {
         // Note, this should return an array where all the keys
         // start with a letter, and all the values are strings.
         // Thus there should be no issue with format=xml.
         $format = new FormatMetadata();
         $format->setSingleLanguage(!$opts['multilang']);
         $format->getContext()->setLanguage($opts['language']);
         $extmetaArray = $format->fetchExtendedMetadata($file);
         if ($opts['extmetadatafilter']) {
             $extmetaArray = array_intersect_key($extmetaArray, array_flip($opts['extmetadatafilter']));
         }
         $vals['extmetadata'] = $extmetaArray;
     }
     if ($mime) {
         $vals['mime'] = $file->getMimeType();
     }
     if ($mediatype) {
         $vals['mediatype'] = $file->getMediaType();
     }
     if ($archive && $file->isOld()) {
         $vals['archivename'] = $file->getArchiveName();
     }
     if ($bitdepth) {
         $vals['bitdepth'] = $file->getBitDepth();
     }
     return $vals;
 }
示例#10
0
 /**
  * Return a random password.
  *
  * @return String new random password
  */
 public static function randomPassword()
 {
     global $wgMinimalPasswordLength;
     // Decide the final password length based on our min password length, stopping at a minimum of 10 chars
     $length = max(10, $wgMinimalPasswordLength);
     // Multiply by 1.25 to get the number of hex characters we need
     $length = $length * 1.25;
     // Generate random hex chars
     $hex = MWCryptRand::generateHex($length);
     // Convert from base 16 to base 32 to get a proper password like string
     return wfBaseConvert($hex, 16, 32);
 }
示例#11
0
 /**
  * @see FileBackendStore::doStoreInternal()
  */
 protected function doStoreInternal(array $params)
 {
     $status = Status::newGood();
     list($dstCont, $dstRel) = $this->resolveStoragePathReal($params['dst']);
     if ($dstRel === null) {
         $status->fatal('backend-fail-invalidpath', $params['dst']);
         return $status;
     }
     // (a) Check the destination container and object
     try {
         unset($this->objCache[$params['dst']]);
         $dContObj = $this->getContainer($dstCont);
         if (empty($params['overwrite']) && $this->fileExists(array('src' => $params['dst'], 'latest' => 1))) {
             $status->fatal('backend-fail-alreadyexists', $params['dst']);
             return $status;
         }
     } catch (NoSuchContainerException $e) {
         $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
         $this->logException($e, __METHOD__, $params);
         return $status;
     } catch (InvalidResponseException $e) {
         $status->fatal('backend-fail-connect', $this->name);
         $this->logException($e, __METHOD__, $params);
         return $status;
     } catch (Exception $e) {
         // some other exception?
         $status->fatal('backend-fail-internal', $this->name);
         $this->logException($e, __METHOD__, $params);
         return $status;
     }
     // (b) Get a SHA-1 hash of the object
     $sha1Hash = sha1_file($params['src']);
     if ($sha1Hash === false) {
         // source doesn't exist?
         $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
         return $status;
     }
     $sha1Hash = wfBaseConvert($sha1Hash, 16, 36, 31);
     // (c) Actually store the object
     try {
         // Create a fresh CF_Object with no fields preloaded.
         // We don't want to preserve headers, metadata, and such.
         $obj = new CF_Object($dContObj, $dstRel, false, false);
         // skip HEAD
         // Note: metadata keys stored as [Upper case char][[Lower case char]...]
         $obj->metadata = array('Sha1base36' => $sha1Hash);
         // The MD5 here will be checked within Swift against its own MD5.
         $obj->set_etag(md5_file($params['src']));
         // Use the same content type as StreamFile for security
         $obj->content_type = $this->getFileProps($params)['mime'];
         // Wikia cnange: use the same logic as for DB row (BAC-1199)
         // Actually write the object in Swift
         $obj->load_from_filename($params['src'], True);
         // calls $obj->write()
     } catch (BadContentTypeException $e) {
         $status->fatal('backend-fail-contenttype', $params['dst']);
         $this->logException($e, __METHOD__, $params);
     } catch (IOException $e) {
         $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
         $this->logException($e, __METHOD__, $params);
     } catch (InvalidResponseException $e) {
         $status->fatal('backend-fail-connect', $this->name);
         $this->logException($e, __METHOD__, $params);
     } catch (Exception $e) {
         // some other exception?
         $status->fatal('backend-fail-internal', $this->name);
         $this->logException($e, __METHOD__, $params);
     }
     wfRunHooks('SwiftFileBackend::doStoreInternal', array($params, &$status));
     return $status;
 }
示例#12
0
 /**
  * @return null|string
  */
 function getSha1()
 {
     return isset($this->mInfo['sha1']) ? wfBaseConvert(strval($this->mInfo['sha1']), 16, 36, 31) : null;
 }
示例#13
0
 /**
  * @param array $time Result of UIDGenerator::millitime()
  * @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201)
  * @throws MWException
  */
 protected function millisecondsSinceEpochBinary(array $time)
 {
     list($sec, $msec) = $time;
     $ts = 1000 * $sec + $msec;
     if ($ts > pow(2, 52)) {
         throw new MWException(__METHOD__ . ': sorry, this function doesn\'t work after the year 144680');
     }
     return substr(wfBaseConvert($ts, 10, 2, 46), -46);
 }
示例#14
0
 protected function getSourceSha1Base36()
 {
     return wfBaseConvert(sha1($this->params['content']), 16, 36, 31);
 }
 /**
  * @param string $ip
  * @param bool $xfor
  * @param string $reason
  * @param int $period
  * @param string $tag
  * @param string $talkTag
  * Lists all users in recent changes who used an IP, newest to oldest down
  * Outputs usernames, latest and earliest found edit date, and count
  * List unique IPs used for each user in time order, list corresponding user agent
  */
 protected function doIPUsersRequest($ip, $xfor = false, $reason = '', $period = 0, $tag = '', $talkTag = '')
 {
     global $wgUser, $wgOut, $wgLang;
     $dbr = wfGetDB(DB_SLAVE);
     # Invalid IPs are passed in as a blank string
     $ip_conds = self::getIpConds($dbr, $ip, $xfor);
     if (!$ip || $ip_conds === false) {
         $wgOut->addWikiMsg('badipaddress');
         return;
     }
     $logType = 'ipusers';
     if ($xfor) {
         $logType .= '-xff';
     }
     # Log the check...
     if (!self::addLogEntry($logType, 'ip', $ip, $reason)) {
         $wgOut->addHTML('<p>' . wfMsgHtml('checkuser-log-fail') . '</p>');
     }
     $ip_conds = $dbr->makeList($ip_conds, LIST_AND);
     $time_conds = $this->getTimeConds($period);
     $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
     # Ordered in descent by timestamp. Can cause large filesorts on range scans.
     # Check how many rows will need sorting ahead of time to see if this is too big.
     if (strpos($ip, '/') !== false) {
         # Quick index check only OK if no time constraint
         if ($period) {
             $rangecount = $dbr->selectField('cu_changes', 'COUNT(*)', array($ip_conds, $time_conds), __METHOD__, array('USE INDEX' => $index));
         } else {
             $rangecount = $dbr->estimateRowCount('cu_changes', '*', array($ip_conds), __METHOD__, array('USE INDEX' => $index));
         }
         // Sorting might take some time...make sure it is there
         wfSuppressWarnings();
         set_time_limit(120);
         wfRestoreWarnings();
     }
     // Are there too many edits?
     if (isset($rangecount) && $rangecount > 10000) {
         $ret = $dbr->select('cu_changes', array('cuc_ip_hex', 'COUNT(*) AS count', 'MIN(cuc_timestamp) AS first', 'MAX(cuc_timestamp) AS last'), array($ip_conds, $time_conds), __METHOD__, array('GROUP BY' => 'cuc_ip_hex', 'ORDER BY' => 'cuc_ip_hex', 'LIMIT' => 5001, 'USE INDEX' => $index));
         # List out each IP that has edits
         $s = '<h5>' . wfMsg('checkuser-too-many') . '</h5>';
         $s .= '<ol>';
         $counter = 0;
         foreach ($ret as $row) {
             if ($counter >= 5000) {
                 $wgOut->addHTML(wfMsgExt('checkuser-limited', array('parse')));
                 break;
             }
             # Convert the IP hexes into normal form
             if (strpos($row->cuc_ip_hex, 'v6-') !== false) {
                 $ip = substr($row->cuc_ip_hex, 3);
                 $ip = IP::HextoOctet($ip);
             } else {
                 $ip = long2ip(wfBaseConvert($row->cuc_ip_hex, 16, 10, 8));
             }
             $s .= '<li><a href="' . $this->getTitle()->escapeLocalURL('user='******'&reason=' . urlencode($reason) . '&checktype=subipusers') . '">' . $ip . '</a>';
             if ($row->first == $row->last) {
                 $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $row->first), true) . ') ';
             } else {
                 $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $row->first), true) . ' -- ' . $wgLang->timeanddate(wfTimestamp(TS_MW, $row->last), true) . ') ';
             }
             $s .= ' [<strong>' . $row->count . "</strong>]</li>\n";
             ++$counter;
         }
         $s .= '</ol>';
         $wgOut->addHTML($s);
         return;
     } elseif (isset($rangecount) && !$rangecount) {
         $s = $this->noMatchesMessage($ip, !$xfor) . "\n";
         $wgOut->addHTML($s);
         return;
     }
     global $wgMemc;
     # OK, do the real query...
     $ret = $dbr->select('cu_changes', array('cuc_user_text', 'cuc_timestamp', 'cuc_user', 'cuc_ip', 'cuc_agent', 'cuc_xff'), array($ip_conds, $time_conds), __METHOD__, array('ORDER BY' => 'cuc_timestamp DESC', 'LIMIT' => 10000, 'USE INDEX' => $index));
     $users_first = $users_last = $users_edits = $users_ids = array();
     if (!$dbr->numRows($ret)) {
         $s = $this->noMatchesMessage($ip, !$xfor) . "\n";
     } else {
         global $wgAuth;
         foreach ($ret as $row) {
             if (!array_key_exists($row->cuc_user_text, $users_edits)) {
                 $users_last[$row->cuc_user_text] = $row->cuc_timestamp;
                 $users_edits[$row->cuc_user_text] = 0;
                 $users_ids[$row->cuc_user_text] = $row->cuc_user;
                 $users_infosets[$row->cuc_user_text] = array();
                 $users_agentsets[$row->cuc_user_text] = array();
             }
             $users_edits[$row->cuc_user_text] += 1;
             $users_first[$row->cuc_user_text] = $row->cuc_timestamp;
             # Treat blank or NULL xffs as empty strings
             $xff = empty($row->cuc_xff) ? null : $row->cuc_xff;
             $xff_ip_combo = array($row->cuc_ip, $xff);
             # Add this IP/XFF combo for this username if it's not already there
             if (!in_array($xff_ip_combo, $users_infosets[$row->cuc_user_text])) {
                 $users_infosets[$row->cuc_user_text][] = $xff_ip_combo;
             }
             # Add this agent string if it's not already there; 10 max.
             if (count($users_agentsets[$row->cuc_user_text]) < 10) {
                 if (!in_array($row->cuc_agent, $users_agentsets[$row->cuc_user_text])) {
                     $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent;
                 }
             }
         }
         $action = $this->getTitle()->escapeLocalURL('action=block');
         $s = "<form name='checkuserblock' id='checkuserblock' action=\"{$action}\" method='post'>";
         $s .= '<div id="checkuserresults"><ul>';
         foreach ($users_edits as $name => $count) {
             $s .= '<li>';
             $s .= Xml::check('users[]', false, array('value' => $name)) . '&#160;';
             # Load user object
             $user = User::newFromName($name, false);
             # Add user tool links
             $s .= $this->sk->userLink(-1, $name) . $this->sk->userToolLinks(-1, $name);
             # Add CheckUser link
             $s .= ' (<a href="' . $this->getTitle()->escapeLocalURL('user='******'&reason=' . urlencode($reason)) . '">' . wfMsgHtml('checkuser-check') . '</a>)';
             # Show edit time range
             if ($users_first[$name] == $users_last[$name]) {
                 $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $users_first[$name]), true) . ') ';
             } else {
                 $s .= ' (' . $wgLang->timeanddate(wfTimestamp(TS_MW, $users_first[$name]), true) . ' -- ' . $wgLang->timeanddate(wfTimestamp(TS_MW, $users_last[$name]), true) . ') ';
             }
             # Total edit count
             $s .= ' [<strong>' . $count . '</strong>]<br />';
             # Check if this user or IP is blocked. If so, give a link to the block log...
             $ip = IP::isIPAddress($name) ? $name : '';
             $flags = $this->userBlockFlags($ip, $users_ids[$name], $user);
             # Show if account is local only
             $authUser = $wgAuth->getUserInstance($user);
             if ($user->getId() && $authUser->getId() === 0) {
                 $flags[] = '<strong>(' . wfMsgHtml('checkuser-localonly') . ')</strong>';
             }
             # Check for extra user rights...
             if ($users_ids[$name]) {
                 if ($user->isLocked()) {
                     $flags[] = '<b>(' . wfMsgHtml('checkuser-locked') . ')</b>';
                 }
                 $list = array();
                 foreach ($user->getGroups() as $group) {
                     $list[] = self::buildGroupLink($group, $user->getName());
                 }
                 $groups = $wgLang->commaList($list);
                 if ($groups) {
                     $flags[] = '<i>(' . $groups . ')</i>';
                 }
             }
             # Check how many accounts the user made recently?
             if ($ip) {
                 $key = wfMemcKey('acctcreate', 'ip', $ip);
                 $count = intval($wgMemc->get($key));
                 if ($count) {
                     $flags[] = '<strong>[' . wfMsgExt('checkuser-accounts', 'parsemag', $wgLang->formatNum($count)) . ']</strong>';
                 }
             }
             $s .= implode(' ', $flags);
             $s .= '<ol>';
             # List out each IP/XFF combo for this username
             for ($i = count($users_infosets[$name]) - 1; $i >= 0; $i--) {
                 $set = $users_infosets[$name][$i];
                 # IP link
                 $s .= '<li>';
                 $s .= '<a href="' . $this->getTitle()->escapeLocalURL('user='******'">' . htmlspecialchars($set[0]) . '</a>';
                 # XFF string, link to /xff search
                 if ($set[1]) {
                     # Flag our trusted proxies
                     list($client, $trusted) = CheckUserHooks::getClientIPfromXFF($set[1], $set[0]);
                     $c = $trusted ? '#F0FFF0' : '#FFFFCC';
                     $s .= '&#160;&#160;&#160;<span style="background-color: ' . $c . '"><strong>XFF</strong>: ';
                     $s .= $this->sk->makeKnownLinkObj($this->getTitle(), htmlspecialchars($set[1]), 'user='******'/xff') . '</span>';
                 }
                 $s .= "</li>\n";
             }
             $s .= '</ol><br /><ol>';
             # List out each agent for this username
             for ($i = count($users_agentsets[$name]) - 1; $i >= 0; $i--) {
                 $agent = $users_agentsets[$name][$i];
                 $s .= '<li><i>' . htmlspecialchars($agent) . "</i></li>\n";
             }
             $s .= '</ol>';
             $s .= '</li>';
         }
         $s .= "</ul></div>\n";
         if ($wgUser->isAllowed('block') && !$wgUser->isBlocked()) {
             $s .= "<fieldset>\n";
             $s .= '<legend>' . wfMsgHtml('checkuser-massblock') . "</legend>\n";
             $s .= '<p>' . wfMsgExt('checkuser-massblock-text', array('parseinline')) . "</p>\n";
             $s .= '<table><tr>' . '<td>' . Xml::check('usetag', false, array('id' => 'usetag')) . '</td>' . '<td>' . Xml::label(wfMsgHtml('checkuser-blocktag'), 'usetag') . '</td>' . '<td>' . Xml::input('tag', 46, $tag, array('id' => 'blocktag')) . '</td>' . '</tr><tr>' . '<td>' . Xml::check('usettag', false, array('id' => 'usettag')) . '</td>' . '<td>' . Xml::label(wfMsgHtml('checkuser-blocktag-talk'), 'usettag') . '</td>' . '<td>' . Xml::input('talktag', 46, $talkTag, array('id' => 'talktag')) . '</td>' . '</tr></table>';
             $s .= '<p>' . wfMsgHtml('checkuser-reason') . '&#160;';
             $s .= Xml::input('blockreason', 46, '', array('maxlength' => '150', 'id' => 'blockreason'));
             $s .= '&#160;' . Xml::submitButton(wfMsgHtml('checkuser-massblock-commit'), array('id' => 'checkuserblocksubmit', 'name' => 'checkuserblock')) . "</p>\n";
             $s .= "</fieldset>\n";
         }
         $s .= '</form>';
     }
     $wgOut->addHTML($s);
 }
示例#16
0
 /**
  * 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.
  *
  * Returns false on failure
  */
 static function sha1Base36($path)
 {
     wfSuppressWarnings();
     $hash = sha1_file($path);
     wfRestoreWarnings();
     if ($hash === false) {
         return false;
     } else {
         return wfBaseConvert($hash, 16, 36, 31);
     }
 }
示例#17
0
 /**
  * @return bool|string
  */
 protected function getSourceSha1Base36()
 {
     wfSuppressWarnings();
     $hash = sha1_file($this->params['src']);
     wfRestoreWarnings();
     if ($hash !== false) {
         $hash = wfBaseConvert($hash, 16, 36, 31);
     }
     return $hash;
 }
示例#18
0
 private function extractRowInfo($row)
 {
     $revision = new Revision($row);
     $title = $revision->getTitle();
     $vals = array();
     if ($this->fld_ids) {
         $vals['revid'] = intval($revision->getId());
         // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed?
         if (!is_null($revision->getParentId())) {
             $vals['parentid'] = intval($revision->getParentId());
         }
     }
     if ($this->fld_flags && $revision->isMinor()) {
         $vals['minor'] = '';
     }
     if ($this->fld_user || $this->fld_userid) {
         if ($revision->isDeleted(Revision::DELETED_USER)) {
             $vals['userhidden'] = '';
         } else {
             if ($this->fld_user) {
                 $vals['user'] = $revision->getUserText();
             }
             $userid = $revision->getUser();
             if (!$userid) {
                 $vals['anon'] = '';
             }
             if ($this->fld_userid) {
                 $vals['userid'] = $userid;
             }
         }
     }
     if ($this->fld_timestamp) {
         $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp());
     }
     if ($this->fld_size) {
         if (!is_null($revision->getSize())) {
             $vals['size'] = intval($revision->getSize());
         } else {
             $vals['size'] = 0;
         }
     }
     if ($this->fld_sha1) {
         if ($revision->getSha1() != '') {
             $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40);
         } else {
             $vals['sha1'] = '';
         }
     }
     if ($this->fld_comment || $this->fld_parsedcomment) {
         if ($revision->isDeleted(Revision::DELETED_COMMENT)) {
             $vals['commenthidden'] = '';
         } else {
             $comment = $revision->getComment();
             if ($this->fld_comment) {
                 $vals['comment'] = $comment;
             }
             if ($this->fld_parsedcomment) {
                 $vals['parsedcomment'] = Linker::formatComment($comment, $title);
             }
         }
     }
     if ($this->fld_tags) {
         if ($row->ts_tags) {
             $tags = explode(',', $row->ts_tags);
             $this->getResult()->setIndexedTagName($tags, 'tag');
             $vals['tags'] = $tags;
         } else {
             $vals['tags'] = array();
         }
     }
     if (!is_null($this->token)) {
         $tokenFunctions = $this->getTokenFunctions();
         foreach ($this->token as $t) {
             $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision);
             if ($val === false) {
                 $this->setWarning("Action '{$t}' is not allowed for the current user");
             } else {
                 $vals[$t . 'token'] = $val;
             }
         }
     }
     $text = null;
     global $wgParser;
     if ($this->fld_content || !is_null($this->difftotext)) {
         $text = $revision->getText();
         // Expand templates after getting section content because
         // template-added sections don't count and Parser::preprocess()
         // will have less input
         if ($this->section !== false) {
             $text = $wgParser->getSection($text, $this->section, false);
             if ($text === false) {
                 $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection');
             }
         }
     }
     if ($this->fld_content && !$revision->isDeleted(Revision::DELETED_TEXT)) {
         if ($this->generateXML) {
             $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), OT_PREPROCESS);
             $dom = $wgParser->preprocessToDom($text);
             if (is_callable(array($dom, 'saveXML'))) {
                 $xml = $dom->saveXML();
             } else {
                 $xml = $dom->__toString();
             }
             $vals['parsetree'] = $xml;
         }
         if ($this->expandTemplates && !$this->parseContent) {
             $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext()));
         }
         if ($this->parseContent) {
             $text = $wgParser->parse($text, $title, ParserOptions::newFromContext($this->getContext()))->getText();
         }
         ApiResult::setContent($vals, $text);
     } elseif ($this->fld_content) {
         $vals['texthidden'] = '';
     }
     if (!is_null($this->diffto) || !is_null($this->difftotext)) {
         global $wgAPIMaxUncachedDiffs;
         static $n = 0;
         // Number of uncached diffs we've had
         if ($n < $wgAPIMaxUncachedDiffs) {
             $vals['diff'] = array();
             $context = new DerivativeContext($this->getContext());
             $context->setTitle($title);
             if (!is_null($this->difftotext)) {
                 $engine = new DifferenceEngine($context);
                 $engine->setText($text, $this->difftotext);
             } else {
                 $engine = new DifferenceEngine($context, $revision->getID(), $this->diffto);
                 $vals['diff']['from'] = $engine->getOldid();
                 $vals['diff']['to'] = $engine->getNewid();
             }
             $difftext = $engine->getDiffBody();
             ApiResult::setContent($vals['diff'], $difftext);
             if (!$engine->wasCacheHit()) {
                 $n++;
             }
         } else {
             $vals['diff']['notcached'] = '';
         }
     }
     return $vals;
 }
示例#19
0
 /**
  * @see FileBackendStore::doStoreInternal()
  * @return Status
  */
 protected function doStoreInternal(array $params)
 {
     $status = Status::newGood();
     list($dstCont, $dstRel) = $this->resolveStoragePathReal($params['dst']);
     if ($dstRel === null) {
         $status->fatal('backend-fail-invalidpath', $params['dst']);
         return $status;
     }
     // (a) Check the destination container and object
     try {
         $dContObj = $this->getContainer($dstCont);
     } catch (NoSuchContainerException $e) {
         $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
         return $status;
     } catch (CloudFilesException $e) {
         // some other exception?
         $this->handleException($e, $status, __METHOD__, $params);
         return $status;
     }
     // (b) Get a SHA-1 hash of the object
     wfSuppressWarnings();
     $sha1Hash = sha1_file($params['src']);
     wfRestoreWarnings();
     if ($sha1Hash === false) {
         // source doesn't exist?
         $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
         return $status;
     }
     $sha1Hash = wfBaseConvert($sha1Hash, 16, 36, 31);
     // (c) Actually store the object
     try {
         // Create a fresh CF_Object with no fields preloaded.
         // We don't want to preserve headers, metadata, and such.
         $obj = new CF_Object($dContObj, $dstRel, false, false);
         // skip HEAD
         $obj->setMetadataValues(array('Sha1base36' => $sha1Hash));
         // The MD5 here will be checked within Swift against its own MD5.
         $obj->set_etag(md5_file($params['src']));
         // Use the same content type as StreamFile for security
         $obj->content_type = StreamFile::contentTypeFromPath($params['dst']);
         if (!strlen($obj->content_type)) {
             // special case
             $obj->content_type = 'unknown/unknown';
         }
         // Set any other custom headers if requested
         if (isset($params['headers'])) {
             $obj->headers += $this->sanitizeHdrs($params['headers']);
         }
         if (!empty($params['async'])) {
             // deferred
             wfSuppressWarnings();
             $fp = fopen($params['src'], 'rb');
             wfRestoreWarnings();
             if (!$fp) {
                 $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
             } else {
                 $op = $obj->write_async($fp, filesize($params['src']), true);
                 $status->value = new SwiftFileOpHandle($this, $params, 'Store', $op);
                 $status->value->resourcesToClose[] = $fp;
                 $status->value->affectedObjects[] = $obj;
             }
         } else {
             // actually write the object in Swift
             $obj->load_from_filename($params['src'], true);
             // calls $obj->write()
             $this->purgeCDNCache(array($obj));
         }
     } catch (CDNNotEnabledException $e) {
         // CDN not enabled; nothing to see here
     } catch (BadContentTypeException $e) {
         $status->fatal('backend-fail-contenttype', $params['dst']);
     } catch (IOException $e) {
         $status->fatal('backend-fail-copy', $params['src'], $params['dst']);
     } catch (CloudFilesException $e) {
         // some other exception?
         $this->handleException($e, $status, __METHOD__, $params);
     }
     return $status;
 }
示例#20
0
 /**
  * Given a string range in a number of formats, return the
  * start and end of the range in hexadecimal. For IPv6.
  *
  * Formats are:
  *     2001:0db8:85a3::7344/96                       CIDR
  *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
  *     2001:0db8:85a3::7344/96                       Single IP
  *
  * @param string $range
  *
  * @return array(string, string)
  */
 private static function parseRange6($range)
 {
     # Expand any IPv6 IP
     $range = IP::sanitizeIP($range);
     // CIDR notation...
     if (strpos($range, '/') !== false) {
         list($network, $bits) = self::parseCIDR6($range);
         if ($network === false) {
             $start = $end = false;
         } else {
             $start = wfBaseConvert($network, 10, 16, 32, false);
             # Turn network to binary (again)
             $end = wfBaseConvert($network, 10, 2, 128);
             # Truncate the last (128-$bits) bits and replace them with ones
             $end = str_pad(substr($end, 0, $bits), 128, 1, STR_PAD_RIGHT);
             # Convert to hex
             $end = wfBaseConvert($end, 2, 16, 32, false);
             # see toHex() comment
             $start = "v6-{$start}";
             $end = "v6-{$end}";
         }
         // Explicit range notation...
     } elseif (strpos($range, '-') !== false) {
         list($start, $end) = array_map('trim', explode('-', $range, 2));
         $start = self::toHex($start);
         $end = self::toHex($end);
         if ($start > $end) {
             $start = $end = false;
         }
     } else {
         # Single IP
         $start = $end = self::toHex($range);
     }
     if ($start === false || $end === false) {
         return array(false, false);
     } else {
         return array($start, $end);
     }
 }
 public function execute()
 {
     global $wgContLang;
     $params = $this->extractRequestParams();
     $this->requireMaxOneParameter($params, 'users', 'ip');
     $prop = array_flip($params['prop']);
     $fld_id = isset($prop['id']);
     $fld_user = isset($prop['user']);
     $fld_userid = isset($prop['userid']);
     $fld_by = isset($prop['by']);
     $fld_byid = isset($prop['byid']);
     $fld_timestamp = isset($prop['timestamp']);
     $fld_expiry = isset($prop['expiry']);
     $fld_reason = isset($prop['reason']);
     $fld_range = isset($prop['range']);
     $fld_flags = isset($prop['flags']);
     $result = $this->getResult();
     $this->addTables('ipblocks');
     $this->addFields('ipb_auto');
     $this->addFieldsIf('ipb_id', $fld_id);
     $this->addFieldsIf(array('ipb_address', 'ipb_user'), $fld_user || $fld_userid);
     $this->addFieldsIf('ipb_by_text', $fld_by);
     $this->addFieldsIf('ipb_by', $fld_byid);
     $this->addFieldsIf('ipb_timestamp', $fld_timestamp);
     $this->addFieldsIf('ipb_expiry', $fld_expiry);
     $this->addFieldsIf('ipb_reason', $fld_reason);
     $this->addFieldsIf(array('ipb_range_start', 'ipb_range_end'), $fld_range);
     $this->addFieldsIf(array('ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk'), $fld_flags);
     $this->addOption('LIMIT', $params['limit'] + 1);
     $this->addTimestampWhereRange('ipb_timestamp', $params['dir'], $params['start'], $params['end']);
     $db = $this->getDB();
     if (isset($params['ids'])) {
         $this->addWhereFld('ipb_id', $params['ids']);
     }
     if (isset($params['users'])) {
         foreach ((array) $params['users'] as $u) {
             $this->prepareUsername($u);
         }
         $this->addWhereFld('ipb_address', $this->usernames);
         $this->addWhereFld('ipb_auto', 0);
     }
     if (isset($params['ip'])) {
         list($ip, $range) = IP::parseCIDR($params['ip']);
         if ($ip && $range) {
             // We got a CIDR range
             if ($range < 16) {
                 $this->dieUsage('CIDR ranges broader than /16 are not accepted', 'cidrtoobroad');
             }
             $lower = wfBaseConvert($ip, 10, 16, 8, false);
             $upper = wfBaseConvert($ip + pow(2, 32 - $range) - 1, 10, 16, 8, false);
         } else {
             $lower = $upper = IP::toHex($params['ip']);
         }
         $prefix = substr($lower, 0, 4);
         # Fairly hard to make a malicious SQL statement out of hex characters,
         # but it is good practice to add quotes
         $lower = $db->addQuotes($lower);
         $upper = $db->addQuotes($upper);
         $this->addWhere(array('ipb_range_start' . $db->buildLike($prefix, $db->anyString()), 'ipb_range_start <= ' . $lower, 'ipb_range_end >= ' . $upper, 'ipb_auto' => 0));
     }
     if (!is_null($params['show'])) {
         $show = array_flip($params['show']);
         /* Check for conflicting parameters. */
         if (isset($show['account']) && isset($show['!account']) || isset($show['ip']) && isset($show['!ip']) || isset($show['range']) && isset($show['!range']) || isset($show['temp']) && isset($show['!temp'])) {
             $this->dieUsageMsg('show');
         }
         $this->addWhereIf('ipb_user = 0', isset($show['!account']));
         $this->addWhereIf('ipb_user != 0', isset($show['account']));
         $this->addWhereIf('ipb_user != 0 OR ipb_range_end > ipb_range_start', isset($show['!ip']));
         $this->addWhereIf('ipb_user = 0 AND ipb_range_end = ipb_range_start', isset($show['ip']));
         $this->addWhereIf('ipb_expiry =  ' . $db->addQuotes($db->getInfinity()), isset($show['!temp']));
         $this->addWhereIf('ipb_expiry != ' . $db->addQuotes($db->getInfinity()), isset($show['temp']));
         $this->addWhereIf("ipb_range_end = ipb_range_start", isset($show['!range']));
         $this->addWhereIf("ipb_range_end > ipb_range_start", isset($show['range']));
     }
     if (!$this->getUser()->isAllowed('hideuser')) {
         $this->addWhereFld('ipb_deleted', 0);
     }
     // Purge expired entries on one in every 10 queries
     if (!mt_rand(0, 10)) {
         Block::purgeExpired();
     }
     $res = $this->select(__METHOD__);
     $count = 0;
     foreach ($res as $row) {
         if (++$count > $params['limit']) {
             // We've had enough
             $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp));
             break;
         }
         $block = array();
         if ($fld_id) {
             $block['id'] = $row->ipb_id;
         }
         if ($fld_user && !$row->ipb_auto) {
             $block['user'] = $row->ipb_address;
         }
         if ($fld_userid && !$row->ipb_auto) {
             $block['userid'] = $row->ipb_user;
         }
         if ($fld_by) {
             $block['by'] = $row->ipb_by_text;
         }
         if ($fld_byid) {
             $block['byid'] = $row->ipb_by;
         }
         if ($fld_timestamp) {
             $block['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ipb_timestamp);
         }
         if ($fld_expiry) {
             $block['expiry'] = $wgContLang->formatExpiry($row->ipb_expiry, TS_ISO_8601);
         }
         if ($fld_reason) {
             $block['reason'] = $row->ipb_reason;
         }
         if ($fld_range && !$row->ipb_auto) {
             $block['rangestart'] = IP::hexToQuad($row->ipb_range_start);
             $block['rangeend'] = IP::hexToQuad($row->ipb_range_end);
         }
         if ($fld_flags) {
             // For clarity, these flags use the same names as their action=block counterparts
             if ($row->ipb_auto) {
                 $block['automatic'] = '';
             }
             if ($row->ipb_anon_only) {
                 $block['anononly'] = '';
             }
             if ($row->ipb_create_account) {
                 $block['nocreate'] = '';
             }
             if ($row->ipb_enable_autoblock) {
                 $block['autoblock'] = '';
             }
             if ($row->ipb_block_email) {
                 $block['noemail'] = '';
             }
             if ($row->ipb_deleted) {
                 $block['hidden'] = '';
             }
             if ($row->ipb_allow_usertalk) {
                 $block['allowusertalk'] = '';
             }
         }
         $fit = $result->addValue(array('query', $this->getModuleName()), null, $block);
         if (!$fit) {
             $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp));
             break;
         }
     }
     $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'block');
 }
 /**
  * Get result information for an image revision
  * @param File f The image
  * @return array Result array
  */
 protected function getInfo($f)
 {
     $vals = array();
     if ($this->fld_timestamp) {
         $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $f->getTimestamp());
     }
     if ($this->fld_user) {
         $vals['user'] = $f->getUser();
         if (!$f->getUser('id')) {
             $vals['anon'] = '';
         }
     }
     if ($this->fld_size) {
         $vals['size'] = intval($f->getSize());
         $vals['width'] = intval($f->getWidth());
         $vals['height'] = intval($f->getHeight());
     }
     if ($this->fld_url) {
         if ($this->scale && !$f->isOld()) {
             $thumb = $f->getThumbnail($this->urlwidth, $this->urlheight);
             if ($thumb) {
                 $vals['thumburl'] = $thumb->getURL();
                 $vals['thumbwidth'] = $thumb->getWidth();
                 $vals['thumbheight'] = $thumb->getHeight();
             }
         }
         $vals['url'] = $f->getURL();
     }
     if ($this->fld_comment) {
         $vals['comment'] = $f->getDescription();
     }
     if ($this->fld_sha1) {
         $vals['sha1'] = wfBaseConvert($f->getSha1(), 36, 16, 40);
     }
     if ($this->fld_metadata) {
         $metadata = unserialize($f->getMetadata());
         $vals['metadata'] = $metadata ? $metadata : null;
         $this->getResult()->setIndexedTagName_recursive($vals['metadata'], 'meta');
     }
     return $vals;
 }
示例#23
0
 function getSha1()
 {
     return wfBaseConvert(strval(@$this->mInfo['sha1']), 16, 36, 31);
 }
 private function doTestDoOperationsFailing()
 {
     $base = self::baseStorePath();
     $fileA = "{$base}/unittest-cont2/a/b/fileA.txt";
     $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
     $fileB = "{$base}/unittest-cont2/a/b/fileB.txt";
     $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
     $fileC = "{$base}/unittest-cont2/a/b/fileC.txt";
     $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
     $fileD = "{$base}/unittest-cont2/a/b/fileD.txt";
     $this->prepare(array('dir' => dirname($fileA)));
     $this->create(array('dst' => $fileA, 'content' => $fileAContents));
     $this->prepare(array('dir' => dirname($fileB)));
     $this->create(array('dst' => $fileB, 'content' => $fileBContents));
     $this->prepare(array('dir' => dirname($fileC)));
     $this->create(array('dst' => $fileC, 'content' => $fileCContents));
     $status = $this->backend->doOperations(array(array('op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1), array('op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1), array('op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1), array('op' => 'move', 'src' => $fileC, 'dst' => $fileD), array('op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1), array('op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1), array('op' => 'delete', 'src' => $fileD), array('op' => 'null')), array('force' => 1));
     $this->assertNotEquals(array(), $status->errors, "Operation had warnings");
     $this->assertEquals(true, $status->isOK(), "Operation batch succeeded");
     $this->assertEquals(8, count($status->success), "Operation batch has correct success array");
     $this->assertEquals(false, $this->backend->fileExists(array('src' => $fileB)), "File does not exist at {$fileB}");
     $this->assertEquals(false, $this->backend->fileExists(array('src' => $fileD)), "File does not exist at {$fileD}");
     $this->assertEquals(true, $this->backend->fileExists(array('src' => $fileA)), "File does not exist at {$fileA}");
     $this->assertEquals(true, $this->backend->fileExists(array('src' => $fileC)), "File exists at {$fileC}");
     $this->assertEquals($fileBContents, $this->backend->getFileContents(array('src' => $fileA)), "Correct file contents of {$fileA}");
     $this->assertEquals(strlen($fileBContents), $this->backend->getFileSize(array('src' => $fileA)), "Correct file size of {$fileA}");
     $this->assertEquals(wfBaseConvert(sha1($fileBContents), 16, 36, 31), $this->backend->getFileSha1Base36(array('src' => $fileA)), "Correct file SHA-1 of {$fileA}");
 }
示例#25
0
 public function execute()
 {
     $user = $this->getUser();
     // Before doing anything at all, let's check permissions
     if (!$user->isAllowed('deletedhistory')) {
         $this->dieUsage('You don\'t have permission to view deleted file information', 'permissiondenied');
     }
     $db = $this->getDB();
     $params = $this->extractRequestParams();
     $prop = array_flip($params['prop']);
     $fld_sha1 = isset($prop['sha1']);
     $fld_timestamp = isset($prop['timestamp']);
     $fld_user = isset($prop['user']);
     $fld_size = isset($prop['size']);
     $fld_dimensions = isset($prop['dimensions']);
     $fld_description = isset($prop['description']) || isset($prop['parseddescription']);
     $fld_mime = isset($prop['mime']);
     $fld_mediatype = isset($prop['mediatype']);
     $fld_metadata = isset($prop['metadata']);
     $fld_bitdepth = isset($prop['bitdepth']);
     $fld_archivename = isset($prop['archivename']);
     $this->addTables('filearchive');
     $this->addFields(ArchivedFile::selectFields());
     $this->addFields(array('fa_id', 'fa_name', 'fa_timestamp', 'fa_deleted'));
     $this->addFieldsIf('fa_sha1', $fld_sha1);
     $this->addFieldsIf(array('fa_user', 'fa_user_text'), $fld_user);
     $this->addFieldsIf(array('fa_height', 'fa_width', 'fa_size'), $fld_dimensions || $fld_size);
     $this->addFieldsIf('fa_description', $fld_description);
     $this->addFieldsIf(array('fa_major_mime', 'fa_minor_mime'), $fld_mime);
     $this->addFieldsIf('fa_media_type', $fld_mediatype);
     $this->addFieldsIf('fa_metadata', $fld_metadata);
     $this->addFieldsIf('fa_bits', $fld_bitdepth);
     $this->addFieldsIf('fa_archive_name', $fld_archivename);
     if (!is_null($params['continue'])) {
         $cont = explode('|', $params['continue']);
         $this->dieContinueUsageIf(count($cont) != 3);
         $op = $params['dir'] == 'descending' ? '<' : '>';
         $cont_from = $db->addQuotes($cont[0]);
         $cont_timestamp = $db->addQuotes($db->timestamp($cont[1]));
         $cont_id = (int) $cont[2];
         $this->dieContinueUsageIf($cont[2] !== (string) $cont_id);
         $this->addWhere("fa_name {$op} {$cont_from} OR " . "(fa_name = {$cont_from} AND " . "(fa_timestamp {$op} {$cont_timestamp} OR " . "(fa_timestamp = {$cont_timestamp} AND " . "fa_id {$op}= {$cont_id} )))");
     }
     // Image filters
     $dir = $params['dir'] == 'descending' ? 'older' : 'newer';
     $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], NS_FILE);
     $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], NS_FILE);
     $this->addWhereRange('fa_name', $dir, $from, $to);
     if (isset($params['prefix'])) {
         $this->addWhere('fa_name' . $db->buildLike($this->titlePartToKey($params['prefix'], NS_FILE), $db->anyString()));
     }
     $sha1Set = isset($params['sha1']);
     $sha1base36Set = isset($params['sha1base36']);
     if ($sha1Set || $sha1base36Set) {
         $sha1 = false;
         if ($sha1Set) {
             $sha1 = strtolower($params['sha1']);
             if (!$this->validateSha1Hash($sha1)) {
                 $this->dieUsage('The SHA1 hash provided is not valid', 'invalidsha1hash');
             }
             $sha1 = wfBaseConvert($sha1, 16, 36, 31);
         } elseif ($sha1base36Set) {
             $sha1 = strtolower($params['sha1base36']);
             if (!$this->validateSha1Base36Hash($sha1)) {
                 $this->dieUsage('The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash');
             }
         }
         if ($sha1) {
             $this->addWhereFld('fa_sha1', $sha1);
         }
     }
     // Exclude files this user can't view.
     if (!$user->isAllowed('deletedtext')) {
         $bitmask = File::DELETED_FILE;
     } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) {
         $bitmask = File::DELETED_FILE | File::DELETED_RESTRICTED;
     } else {
         $bitmask = 0;
     }
     if ($bitmask) {
         $this->addWhere($this->getDB()->bitAnd('fa_deleted', $bitmask) . " != {$bitmask}");
     }
     $limit = $params['limit'];
     $this->addOption('LIMIT', $limit + 1);
     $sort = $params['dir'] == 'descending' ? ' DESC' : '';
     $this->addOption('ORDER BY', array('fa_name' . $sort, 'fa_timestamp' . $sort, 'fa_id' . $sort));
     $res = $this->select(__METHOD__);
     $count = 0;
     $result = $this->getResult();
     foreach ($res as $row) {
         if (++$count > $limit) {
             // We've reached the one extra which shows that there are
             // additional pages to be had. Stop here...
             $this->setContinueEnumParameter('continue', "{$row->fa_name}|{$row->fa_timestamp}|{$row->fa_id}");
             break;
         }
         $file = array();
         $file['id'] = (int) $row->fa_id;
         $file['name'] = $row->fa_name;
         $title = Title::makeTitle(NS_FILE, $row->fa_name);
         self::addTitleInfo($file, $title);
         if ($fld_description && Revision::userCanBitfield($row->fa_deleted, File::DELETED_COMMENT, $user)) {
             $file['description'] = $row->fa_description;
             if (isset($prop['parseddescription'])) {
                 $file['parseddescription'] = Linker::formatComment($row->fa_description, $title);
             }
         }
         if ($fld_user && Revision::userCanBitfield($row->fa_deleted, File::DELETED_USER, $user)) {
             $file['userid'] = (int) $row->fa_user;
             $file['user'] = $row->fa_user_text;
         }
         if ($fld_sha1) {
             $file['sha1'] = wfBaseConvert($row->fa_sha1, 36, 16, 40);
         }
         if ($fld_timestamp) {
             $file['timestamp'] = wfTimestamp(TS_ISO_8601, $row->fa_timestamp);
         }
         if ($fld_size || $fld_dimensions) {
             $file['size'] = $row->fa_size;
             $pageCount = ArchivedFile::newFromRow($row)->pageCount();
             if ($pageCount !== false) {
                 $file['pagecount'] = $pageCount;
             }
             $file['height'] = $row->fa_height;
             $file['width'] = $row->fa_width;
         }
         if ($fld_mediatype) {
             $file['mediatype'] = $row->fa_media_type;
         }
         if ($fld_metadata) {
             $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData(unserialize($row->fa_metadata), $result) : null;
         }
         if ($fld_bitdepth) {
             $file['bitdepth'] = $row->fa_bits;
         }
         if ($fld_mime) {
             $file['mime'] = "{$row->fa_major_mime}/{$row->fa_minor_mime}";
         }
         if ($fld_archivename && !is_null($row->fa_archive_name)) {
             $file['archivename'] = $row->fa_archive_name;
         }
         if ($row->fa_deleted & File::DELETED_FILE) {
             $file['filehidden'] = true;
         }
         if ($row->fa_deleted & File::DELETED_COMMENT) {
             $file['commenthidden'] = true;
         }
         if ($row->fa_deleted & File::DELETED_USER) {
             $file['userhidden'] = true;
         }
         if ($row->fa_deleted & File::DELETED_RESTRICTED) {
             // This file is deleted for normal admins
             $file['suppressed'] = true;
         }
         $fit = $result->addValue(array('query', $this->getModuleName()), null, $file);
         if (!$fit) {
             $this->setContinueEnumParameter('continue', "{$row->fa_name}|{$row->fa_timestamp}|{$row->fa_id}");
             break;
         }
     }
     $result->addIndexedTagName(array('query', $this->getModuleName()), 'fa');
 }
示例#26
0
 public function execute()
 {
     $user = $this->getUser();
     // Before doing anything at all, let's check permissions
     if (!$user->isAllowed('deletedhistory')) {
         $this->dieUsage('You don\'t have permission to view deleted file information', 'permissiondenied');
     }
     $db = $this->getDB();
     $params = $this->extractRequestParams();
     $prop = array_flip($params['prop']);
     $fld_sha1 = isset($prop['sha1']);
     $fld_timestamp = isset($prop['timestamp']);
     $fld_user = isset($prop['user']);
     $fld_size = isset($prop['size']);
     $fld_dimensions = isset($prop['dimensions']);
     $fld_description = isset($prop['description']) || isset($prop['parseddescription']);
     $fld_mime = isset($prop['mime']);
     $fld_mediatype = isset($prop['mediatype']);
     $fld_metadata = isset($prop['metadata']);
     $fld_bitdepth = isset($prop['bitdepth']);
     $fld_archivename = isset($prop['archivename']);
     $this->addTables('filearchive');
     $this->addFields(array('fa_name', 'fa_deleted'));
     $this->addFieldsIf('fa_storage_key', $fld_sha1);
     $this->addFieldsIf('fa_timestamp', $fld_timestamp);
     $this->addFieldsIf(array('fa_user', 'fa_user_text'), $fld_user);
     $this->addFieldsIf(array('fa_height', 'fa_width', 'fa_size'), $fld_dimensions || $fld_size);
     $this->addFieldsIf('fa_description', $fld_description);
     $this->addFieldsIf(array('fa_major_mime', 'fa_minor_mime'), $fld_mime);
     $this->addFieldsIf('fa_media_type', $fld_mediatype);
     $this->addFieldsIf('fa_metadata', $fld_metadata);
     $this->addFieldsIf('fa_bits', $fld_bitdepth);
     $this->addFieldsIf('fa_archive_name', $fld_archivename);
     if (!is_null($params['continue'])) {
         $cont = explode('|', $params['continue']);
         if (count($cont) != 1) {
             $this->dieUsage("Invalid continue param. You should pass the " . "original value returned by the previous query", "_badcontinue");
         }
         $op = $params['dir'] == 'descending' ? '<' : '>';
         $cont_from = $db->addQuotes($cont[0]);
         $this->addWhere("fa_name {$op}= {$cont_from}");
     }
     // Image filters
     $dir = $params['dir'] == 'descending' ? 'older' : 'newer';
     $from = is_null($params['from']) ? null : $this->titlePartToKey($params['from']);
     if (!is_null($params['continue'])) {
         $from = $params['continue'];
     }
     $to = is_null($params['to']) ? null : $this->titlePartToKey($params['to']);
     $this->addWhereRange('fa_name', $dir, $from, $to);
     if (isset($params['prefix'])) {
         $this->addWhere('fa_name' . $db->buildLike($this->titlePartToKey($params['prefix']), $db->anyString()));
     }
     $sha1Set = isset($params['sha1']);
     $sha1base36Set = isset($params['sha1base36']);
     if ($sha1Set || $sha1base36Set) {
         global $wgMiserMode;
         if ($wgMiserMode) {
             $this->dieUsage('Search by hash disabled in Miser Mode', 'hashsearchdisabled');
         }
         $sha1 = false;
         if ($sha1Set) {
             if (!$this->validateSha1Hash($params['sha1'])) {
                 $this->dieUsage('The SHA1 hash provided is not valid', 'invalidsha1hash');
             }
             $sha1 = wfBaseConvert($params['sha1'], 16, 36, 31);
         } elseif ($sha1base36Set) {
             if (!$this->validateSha1Base36Hash($params['sha1base36'])) {
                 $this->dieUsage('The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash');
             }
             $sha1 = $params['sha1base36'];
         }
         if ($sha1) {
             $this->addWhere('fa_storage_key ' . $db->buildLike("{$sha1}.", $db->anyString()));
         }
     }
     if (!$user->isAllowed('suppressrevision')) {
         // Filter out revisions that the user is not allowed to see. There
         // is no way to indicate that we have skipped stuff because the
         // continuation parameter is fa_name
         // Note that this field is unindexed. This should however not be
         // a big problem as files with fa_deleted are rare
         $this->addWhereFld('fa_deleted', 0);
     }
     $limit = $params['limit'];
     $this->addOption('LIMIT', $limit + 1);
     $sort = $params['dir'] == 'descending' ? ' DESC' : '';
     $this->addOption('ORDER BY', 'fa_name' . $sort);
     $res = $this->select(__METHOD__);
     $count = 0;
     $result = $this->getResult();
     foreach ($res as $row) {
         if (++$count > $limit) {
             // We've reached the one extra which shows that there are additional pages to be had. Stop here...
             $this->setContinueEnumParameter('continue', $row->fa_name);
             break;
         }
         $file = array();
         $file['name'] = $row->fa_name;
         $title = Title::makeTitle(NS_FILE, $row->fa_name);
         self::addTitleInfo($file, $title);
         if ($fld_sha1) {
             $file['sha1'] = wfBaseConvert(LocalRepo::getHashFromKey($row->fa_storage_key), 36, 16, 40);
         }
         if ($fld_timestamp) {
             $file['timestamp'] = wfTimestamp(TS_ISO_8601, $row->fa_timestamp);
         }
         if ($fld_user) {
             $file['userid'] = $row->fa_user;
             $file['user'] = $row->fa_user_text;
         }
         if ($fld_size || $fld_dimensions) {
             $file['size'] = $row->fa_size;
             $pageCount = ArchivedFile::newFromRow($row)->pageCount();
             if ($pageCount !== false) {
                 $vals['pagecount'] = $pageCount;
             }
             $file['height'] = $row->fa_height;
             $file['width'] = $row->fa_width;
         }
         if ($fld_description) {
             $file['description'] = $row->fa_description;
             if (isset($prop['parseddescription'])) {
                 $file['parseddescription'] = Linker::formatComment($row->fa_description, $title);
             }
         }
         if ($fld_mediatype) {
             $file['mediatype'] = $row->fa_media_type;
         }
         if ($fld_metadata) {
             $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData(unserialize($row->fa_metadata), $result) : null;
         }
         if ($fld_bitdepth) {
             $file['bitdepth'] = $row->fa_bits;
         }
         if ($fld_mime) {
             $file['mime'] = "{$row->fa_major_mime}/{$row->fa_minor_mime}";
         }
         if ($fld_archivename && !is_null($row->fa_archive_name)) {
             $file['archivename'] = $row->fa_archive_name;
         }
         if ($row->fa_deleted & File::DELETED_FILE) {
             $file['filehidden'] = '';
         }
         if ($row->fa_deleted & File::DELETED_COMMENT) {
             $file['commenthidden'] = '';
         }
         if ($row->fa_deleted & File::DELETED_USER) {
             $file['userhidden'] = '';
         }
         if ($row->fa_deleted & File::DELETED_RESTRICTED) {
             // This file is deleted for normal admins
             $file['suppressed'] = '';
         }
         $fit = $result->addValue(array('query', $this->getModuleName()), null, $file);
         if (!$fit) {
             $this->setContinueEnumParameter('continue', $row->fa_name);
             break;
         }
     }
     $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'fa');
 }
示例#27
0
 /**
  * Generate a new random session ID
  * @return string
  */
 public function generateSessionId()
 {
     do {
         $id = wfBaseConvert(\MWCryptRand::generateHex(40), 16, 32, 32);
         $key = wfMemcKey('MWSession', $id);
     } while (isset($this->allSessionIds[$id]) || is_array($this->store->get($key)));
     return $id;
 }
 /**
  * Extract information from the Revision
  *
  * @param Revision $revision
  * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set
  * @return array
  */
 protected function extractRevisionInfo(Revision $revision, $row)
 {
     $title = $revision->getTitle();
     $user = $this->getUser();
     $vals = array();
     $anyHidden = false;
     if ($this->fld_ids) {
         $vals['revid'] = intval($revision->getId());
         if (!is_null($revision->getParentId())) {
             $vals['parentid'] = intval($revision->getParentId());
         }
     }
     if ($this->fld_flags) {
         $vals['minor'] = $revision->isMinor();
     }
     if ($this->fld_user || $this->fld_userid) {
         if ($revision->isDeleted(Revision::DELETED_USER)) {
             $vals['userhidden'] = true;
             $anyHidden = true;
         }
         if ($revision->userCan(Revision::DELETED_USER, $user)) {
             if ($this->fld_user) {
                 $vals['user'] = $revision->getUserText(Revision::RAW);
             }
             $userid = $revision->getUser(Revision::RAW);
             if (!$userid) {
                 $vals['anon'] = true;
             }
             if ($this->fld_userid) {
                 $vals['userid'] = $userid;
             }
         }
     }
     if ($this->fld_timestamp) {
         $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp());
     }
     if ($this->fld_size) {
         if (!is_null($revision->getSize())) {
             $vals['size'] = intval($revision->getSize());
         } else {
             $vals['size'] = 0;
         }
     }
     if ($this->fld_sha1) {
         if ($revision->isDeleted(Revision::DELETED_TEXT)) {
             $vals['sha1hidden'] = true;
             $anyHidden = true;
         }
         if ($revision->userCan(Revision::DELETED_TEXT, $user)) {
             if ($revision->getSha1() != '') {
                 $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40);
             } else {
                 $vals['sha1'] = '';
             }
         }
     }
     if ($this->fld_contentmodel) {
         $vals['contentmodel'] = $revision->getContentModel();
     }
     if ($this->fld_comment || $this->fld_parsedcomment) {
         if ($revision->isDeleted(Revision::DELETED_COMMENT)) {
             $vals['commenthidden'] = true;
             $anyHidden = true;
         }
         if ($revision->userCan(Revision::DELETED_COMMENT, $user)) {
             $comment = $revision->getComment(Revision::RAW);
             if ($this->fld_comment) {
                 $vals['comment'] = $comment;
             }
             if ($this->fld_parsedcomment) {
                 $vals['parsedcomment'] = Linker::formatComment($comment, $title);
             }
         }
     }
     if ($this->fld_tags) {
         if ($row->ts_tags) {
             $tags = explode(',', $row->ts_tags);
             ApiResult::setIndexedTagName($tags, 'tag');
             $vals['tags'] = $tags;
         } else {
             $vals['tags'] = array();
         }
     }
     $content = null;
     global $wgParser;
     if ($this->fetchContent) {
         $content = $revision->getContent(Revision::FOR_THIS_USER, $this->getUser());
         // Expand templates after getting section content because
         // template-added sections don't count and Parser::preprocess()
         // will have less input
         if ($content && $this->section !== false) {
             $content = $content->getSection($this->section, false);
             if (!$content) {
                 $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection');
             }
         }
         if ($revision->isDeleted(Revision::DELETED_TEXT)) {
             $vals['texthidden'] = true;
             $anyHidden = true;
         } elseif (!$content) {
             $vals['textmissing'] = true;
         }
     }
     if ($this->fld_content && $content) {
         $text = null;
         if ($this->generateXML) {
             if ($content->getModel() === CONTENT_MODEL_WIKITEXT) {
                 $t = $content->getNativeData();
                 # note: don't set $text
                 $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), Parser::OT_PREPROCESS);
                 $dom = $wgParser->preprocessToDom($t);
                 if (is_callable(array($dom, 'saveXML'))) {
                     $xml = $dom->saveXML();
                 } else {
                     $xml = $dom->__toString();
                 }
                 $vals['parsetree'] = $xml;
             } else {
                 $vals['badcontentformatforparsetree'] = true;
                 $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel());
             }
         }
         if ($this->expandTemplates && !$this->parseContent) {
             #XXX: implement template expansion for all content types in ContentHandler?
             if ($content->getModel() === CONTENT_MODEL_WIKITEXT) {
                 $text = $content->getNativeData();
                 $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext()));
             } else {
                 $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel());
                 $vals['badcontentformat'] = true;
                 $text = false;
             }
         }
         if ($this->parseContent) {
             $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext()));
             $text = $po->getText();
         }
         if ($text === null) {
             $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
             $model = $content->getModel();
             if (!$content->isSupportedFormat($format)) {
                 $name = $title->getPrefixedDBkey();
                 $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}");
                 $vals['badcontentformat'] = true;
                 $text = false;
             } else {
                 $text = $content->serialize($format);
                 // always include format and model.
                 // Format is needed to deserialize, model is needed to interpret.
                 $vals['contentformat'] = $format;
                 $vals['contentmodel'] = $model;
             }
         }
         if ($text !== false) {
             ApiResult::setContentValue($vals, 'content', $text);
         }
     }
     if ($content && (!is_null($this->diffto) || !is_null($this->difftotext))) {
         static $n = 0;
         // Number of uncached diffs we've had
         if ($n < $this->getConfig()->get('APIMaxUncachedDiffs')) {
             $vals['diff'] = array();
             $context = new DerivativeContext($this->getContext());
             $context->setTitle($title);
             $handler = $revision->getContentHandler();
             if (!is_null($this->difftotext)) {
                 $model = $title->getContentModel();
                 if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) {
                     $name = $title->getPrefixedDBkey();
                     $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}");
                     $vals['diff']['badcontentformat'] = true;
                     $engine = null;
                 } else {
                     $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat);
                     $engine = $handler->createDifferenceEngine($context);
                     $engine->setContent($content, $difftocontent);
                 }
             } else {
                 $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto);
                 $vals['diff']['from'] = $engine->getOldid();
                 $vals['diff']['to'] = $engine->getNewid();
             }
             if ($engine) {
                 $difftext = $engine->getDiffBody();
                 ApiResult::setContentValue($vals['diff'], 'body', $difftext);
                 if (!$engine->wasCacheHit()) {
                     $n++;
                 }
             }
         } else {
             $vals['diff']['notcached'] = true;
         }
     }
     if ($anyHidden && $revision->isDeleted(Revision::DELETED_RESTRICTED)) {
         $vals['suppressed'] = true;
     }
     return $vals;
 }
 /**
  * Extracts from a single sql row the data needed to describe one recent change.
  *
  * @param stdClass $row The row from which to extract the data.
  * @return array An array mapping strings (descriptors) to their respective string values.
  * @access public
  */
 public function extractRowInfo($row)
 {
     /* Determine the title of the page that has been changed. */
     $title = Title::makeTitle($row->rc_namespace, $row->rc_title);
     $user = $this->getUser();
     /* Our output data. */
     $vals = array();
     $type = intval($row->rc_type);
     $vals['type'] = RecentChange::parseFromRCType($type);
     $anyHidden = false;
     /* Create a new entry in the result for the title. */
     if ($this->fld_title || $this->fld_ids) {
         if ($type === RC_LOG && $row->rc_deleted & LogPage::DELETED_ACTION) {
             $vals['actionhidden'] = true;
             $anyHidden = true;
         }
         if ($type !== RC_LOG || LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) {
             if ($this->fld_title) {
                 ApiQueryBase::addTitleInfo($vals, $title);
             }
             if ($this->fld_ids) {
                 $vals['pageid'] = intval($row->rc_cur_id);
                 $vals['revid'] = intval($row->rc_this_oldid);
                 $vals['old_revid'] = intval($row->rc_last_oldid);
             }
         }
     }
     if ($this->fld_ids) {
         $vals['rcid'] = intval($row->rc_id);
     }
     /* Add user data and 'anon' flag, if user is anonymous. */
     if ($this->fld_user || $this->fld_userid) {
         if ($row->rc_deleted & Revision::DELETED_USER) {
             $vals['userhidden'] = true;
             $anyHidden = true;
         }
         if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_USER, $user)) {
             if ($this->fld_user) {
                 $vals['user'] = $row->rc_user_text;
             }
             if ($this->fld_userid) {
                 $vals['userid'] = $row->rc_user;
             }
             if (!$row->rc_user) {
                 $vals['anon'] = true;
             }
         }
     }
     /* Add flags, such as new, minor, bot. */
     if ($this->fld_flags) {
         $vals['bot'] = (bool) $row->rc_bot;
         $vals['new'] = $row->rc_type == RC_NEW;
         $vals['minor'] = (bool) $row->rc_minor;
     }
     /* Add sizes of each revision. (Only available on 1.10+) */
     if ($this->fld_sizes) {
         $vals['oldlen'] = intval($row->rc_old_len);
         $vals['newlen'] = intval($row->rc_new_len);
     }
     /* Add the timestamp. */
     if ($this->fld_timestamp) {
         $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp);
     }
     /* Add edit summary / log summary. */
     if ($this->fld_comment || $this->fld_parsedcomment) {
         if ($row->rc_deleted & Revision::DELETED_COMMENT) {
             $vals['commenthidden'] = true;
             $anyHidden = true;
         }
         if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_COMMENT, $user)) {
             if ($this->fld_comment && isset($row->rc_comment)) {
                 $vals['comment'] = $row->rc_comment;
             }
             if ($this->fld_parsedcomment && isset($row->rc_comment)) {
                 $vals['parsedcomment'] = Linker::formatComment($row->rc_comment, $title);
             }
         }
     }
     if ($this->fld_redirect) {
         $vals['redirect'] = (bool) $row->page_is_redirect;
     }
     /* Add the patrolled flag */
     if ($this->fld_patrolled) {
         $vals['patrolled'] = $row->rc_patrolled == 1;
         $vals['unpatrolled'] = ChangesList::isUnpatrolled($row, $user);
     }
     if ($this->fld_loginfo && $row->rc_type == RC_LOG) {
         if ($row->rc_deleted & LogPage::DELETED_ACTION) {
             $vals['actionhidden'] = true;
             $anyHidden = true;
         }
         if (LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) {
             $vals['logid'] = intval($row->rc_logid);
             $vals['logtype'] = $row->rc_log_type;
             $vals['logaction'] = $row->rc_log_action;
             $vals['logparams'] = LogFormatter::newFromRow($row)->formatParametersForApi();
         }
     }
     if ($this->fld_tags) {
         if ($row->ts_tags) {
             $tags = explode(',', $row->ts_tags);
             ApiResult::setIndexedTagName($tags, 'tag');
             $vals['tags'] = $tags;
         } else {
             $vals['tags'] = array();
         }
     }
     if ($this->fld_sha1 && $row->rev_sha1 !== null) {
         if ($row->rev_deleted & Revision::DELETED_TEXT) {
             $vals['sha1hidden'] = true;
             $anyHidden = true;
         }
         if (Revision::userCanBitfield($row->rev_deleted, Revision::DELETED_TEXT, $user)) {
             if ($row->rev_sha1 !== '') {
                 $vals['sha1'] = wfBaseConvert($row->rev_sha1, 36, 16, 40);
             } else {
                 $vals['sha1'] = '';
             }
         }
     }
     if (!is_null($this->token)) {
         $tokenFunctions = $this->getTokenFunctions();
         foreach ($this->token as $t) {
             $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id, $title, RecentChange::newFromRow($row));
             if ($val === false) {
                 $this->setWarning("Action '{$t}' is not allowed for the current user");
             } else {
                 $vals[$t . 'token'] = $val;
             }
         }
     }
     if ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) {
         $vals['suppressed'] = true;
     }
     return $vals;
 }
 /**
  * @param $resultPageSet ApiPageSet
  * @return void
  */
 private function run($resultPageSet = null)
 {
     $repo = $this->mRepo;
     if (!$repo instanceof LocalRepo) {
         $this->dieUsage('Local file repository does not support querying all images', 'unsupportedrepo');
     }
     $prefix = $this->getModulePrefix();
     $db = $this->getDB();
     $params = $this->extractRequestParams();
     // Table and return fields
     $this->addTables('image');
     $prop = array_flip($params['prop']);
     $this->addFields(LocalFile::selectFields());
     $ascendingOrder = true;
     if ($params['dir'] == 'descending' || $params['dir'] == 'older') {
         $ascendingOrder = false;
     }
     if ($params['sort'] == 'name') {
         // Check mutually exclusive params
         $disallowed = array('start', 'end', 'user');
         foreach ($disallowed as $pname) {
             if (isset($params[$pname])) {
                 $this->dieUsage("Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=timestamp", 'badparams');
             }
         }
         if ($params['filterbots'] != 'all') {
             $this->dieUsage("Parameter '{$prefix}filterbots' can only be used with {$prefix}sort=timestamp", 'badparams');
         }
         // Pagination
         if (!is_null($params['continue'])) {
             $cont = explode('|', $params['continue']);
             $this->dieContinueUsageIf(count($cont) != 1);
             $op = $ascendingOrder ? '>' : '<';
             $continueFrom = $db->addQuotes($cont[0]);
             $this->addWhere("img_name {$op}= {$continueFrom}");
         }
         // Image filters
         $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], NS_FILE);
         $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], NS_FILE);
         $this->addWhereRange('img_name', $ascendingOrder ? 'newer' : 'older', $from, $to);
         if (isset($params['prefix'])) {
             $this->addWhere('img_name' . $db->buildLike($this->titlePartToKey($params['prefix'], NS_FILE), $db->anyString()));
         }
     } else {
         // Check mutually exclusive params
         $disallowed = array('from', 'to', 'prefix');
         foreach ($disallowed as $pname) {
             if (isset($params[$pname])) {
                 $this->dieUsage("Parameter '{$prefix}{$pname}' can only be used with {$prefix}sort=name", 'badparams');
             }
         }
         if (!is_null($params['user']) && $params['filterbots'] != 'all') {
             // Since filterbots checks if each user has the bot right, it
             // doesn't make sense to use it with user
             $this->dieUsage("Parameters '{$prefix}user' and '{$prefix}filterbots' cannot be used together", 'badparams');
         }
         // Pagination
         $this->addTimestampWhereRange('img_timestamp', $ascendingOrder ? 'newer' : 'older', $params['start'], $params['end']);
         // Include in ORDER BY for uniqueness
         $this->addWhereRange('img_name', $ascendingOrder ? 'newer' : 'older', null, null);
         if (!is_null($params['continue'])) {
             $cont = explode('|', $params['continue']);
             $this->dieContinueUsageIf(count($cont) != 2);
             $op = $ascendingOrder ? '>' : '<';
             $continueTimestamp = $db->addQuotes($db->timestamp($cont[0]));
             $continueName = $db->addQuotes($cont[1]);
             $this->addWhere("img_timestamp {$op} {$continueTimestamp} OR " . "(img_timestamp = {$continueTimestamp} AND " . "img_name {$op}= {$continueName})");
         }
         // Image filters
         if (!is_null($params['user'])) {
             $this->addWhereFld('img_user_text', $params['user']);
         }
         if ($params['filterbots'] != 'all') {
             $this->addTables('user_groups');
             $this->addJoinConds(array('user_groups' => array('LEFT JOIN', array('ug_group' => User::getGroupsWithPermission('bot'), 'ug_user = img_user'))));
             $groupCond = $params['filterbots'] == 'nobots' ? 'NULL' : 'NOT NULL';
             $this->addWhere("ug_group IS {$groupCond}");
         }
     }
     // Filters not depending on sort
     if (isset($params['minsize'])) {
         $this->addWhere('img_size>=' . intval($params['minsize']));
     }
     if (isset($params['maxsize'])) {
         $this->addWhere('img_size<=' . intval($params['maxsize']));
     }
     $sha1 = false;
     if (isset($params['sha1'])) {
         $sha1 = strtolower($params['sha1']);
         if (!$this->validateSha1Hash($sha1)) {
             $this->dieUsage('The SHA1 hash provided is not valid', 'invalidsha1hash');
         }
         $sha1 = wfBaseConvert($sha1, 16, 36, 31);
     } elseif (isset($params['sha1base36'])) {
         $sha1 = strtolower($params['sha1base36']);
         if (!$this->validateSha1Base36Hash($sha1)) {
             $this->dieUsage('The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash');
         }
     }
     if ($sha1) {
         $this->addWhereFld('img_sha1', $sha1);
     }
     if (!is_null($params['mime'])) {
         global $wgMiserMode;
         if ($wgMiserMode) {
             $this->dieUsage('MIME search disabled in Miser Mode', 'mimesearchdisabled');
         }
         list($major, $minor) = File::splitMime($params['mime']);
         $this->addWhereFld('img_major_mime', $major);
         $this->addWhereFld('img_minor_mime', $minor);
     }
     $limit = $params['limit'];
     $this->addOption('LIMIT', $limit + 1);
     $sortFlag = '';
     if (!$ascendingOrder) {
         $sortFlag = ' DESC';
     }
     if ($params['sort'] == 'timestamp') {
         $this->addOption('ORDER BY', 'img_timestamp' . $sortFlag);
         if (!is_null($params['user'])) {
             $this->addOption('USE INDEX', array('image' => 'img_usertext_timestamp'));
         } else {
             $this->addOption('USE INDEX', array('image' => 'img_timestamp'));
         }
     } else {
         $this->addOption('ORDER BY', 'img_name' . $sortFlag);
     }
     $res = $this->select(__METHOD__);
     $titles = array();
     $count = 0;
     $result = $this->getResult();
     foreach ($res as $row) {
         if (++$count > $limit) {
             // We've reached the one extra which shows that there are
             // additional pages to be had. Stop here...
             if ($params['sort'] == 'name') {
                 $this->setContinueEnumParameter('continue', $row->img_name);
             } else {
                 $this->setContinueEnumParameter('continue', "{$row->img_timestamp}|{$row->img_name}");
             }
             break;
         }
         if (is_null($resultPageSet)) {
             $file = $repo->newFileFromRow($row);
             $info = array_merge(array('name' => $row->img_name), ApiQueryImageInfo::getInfo($file, $prop, $result));
             self::addTitleInfo($info, $file->getTitle());
             $fit = $result->addValue(array('query', $this->getModuleName()), null, $info);
             if (!$fit) {
                 if ($params['sort'] == 'name') {
                     $this->setContinueEnumParameter('continue', $row->img_name);
                 } else {
                     $this->setContinueEnumParameter('continue', "{$row->img_timestamp}|{$row->img_name}");
                 }
                 break;
             }
         } else {
             $titles[] = Title::makeTitle(NS_FILE, $row->img_name);
         }
     }
     if (is_null($resultPageSet)) {
         $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'img');
     } else {
         $resultPageSet->populateFromTitles($titles);
     }
 }