/** * Builds a normalized cache key from a given key. * * If the given key is a string containing alphanumeric characters only and no more than 32 characters, * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]]. * * @param mixed $key the key to be normalized * @return string the generated cache key */ public function buildKey($key) { if (is_string($key)) { $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key); } else { $key = md5(json_encode($key)); } return $this->keyPrefix . $key; }
public static function compTokens($token, $trueToken) { $token = base64_decode(str_replace('.', '+', $token)); $n = StringHelper::byteLength($token); if ($n <= static::CSRF_MASK_LENGTH) { return false; } $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); $token = static::xorTokens($mask, $token); return $token === $trueToken; }
/** * @dataProvider rightRanges */ public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedContent) { $dataFile = \Yii::getAlias('@yiiunit/data/web/data.txt'); $fullContent = file_get_contents($dataFile); $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; ob_start(); $this->response->sendFile($dataFile)->send(); $content = ob_get_clean(); $this->assertEquals($expectedContent, $content); $this->assertEquals(206, $this->response->statusCode); $headers = $this->response->headers; $this->assertEquals("bytes", $headers->get('Accept-Ranges')); $this->assertEquals("bytes " . $expectedHeader . '/' . StringHelper::byteLength($fullContent), $headers->get('Content-Range')); $this->assertEquals('text/plain', $headers->get('Content-Type')); $this->assertEquals("{$length}", $headers->get('Content-Length')); }
/** * * @param string $url * @return self */ protected static function setDomains($url) { self::$baseFolder = ''; if (empty(self::$homeUrl)) { self::$homeUrl = rtrim(StringHelper::dirname($_SERVER['PHP_SELF']), '/'); } if (empty(self::$baseFolder)) { if ($str = mb_stristr(self::$homeUrl, 'admin', TRUE)) { self::$baseFolder = $str . "admin"; } if ($str == false && !empty(self::$homeUrl)) { self::$baseFolder = rtrim(self::$homeUrl, '/'); } self::$baseFolder = rtrim(self::$baseFolder, '/'); } $url = StringHelper::byteSubstr($url, StringHelper::byteLength(self::$baseFolder), StringHelper::byteLength($url)); self::$domains = explode('/', ltrim($url, '/')); return self::$domains; }
/** * Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead. * @param string $pattern * @param boolean $caseSensitive * @throws \yii\base\InvalidParamException * @return array with keys: (string) pattern, (int) flags, (int|boolean)firstWildcard */ private static function parseExcludePattern($pattern, $caseSensitive) { if (!is_string($pattern)) { throw new InvalidParamException('Exclude/include pattern must be a string.'); } $result = ['pattern' => $pattern, 'flags' => 0, 'firstWildcard' => false]; if (!$caseSensitive) { $result['flags'] |= self::PATTERN_CASE_INSENSITIVE; } if (!isset($pattern[0])) { return $result; } if ($pattern[0] == '!') { $result['flags'] |= self::PATTERN_NEGATIVE; $pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern)); } if (StringHelper::byteLength($pattern) && StringHelper::byteSubstr($pattern, -1, 1) == '/') { $pattern = StringHelper::byteSubstr($pattern, 0, -1); $result['flags'] |= self::PATTERN_MUSTBEDIR; } if (strpos($pattern, '/') === false) { $result['flags'] |= self::PATTERN_NODIR; } $result['firstWildcard'] = self::firstWildcardInPattern($pattern); if ($pattern[0] == '*' && self::firstWildcardInPattern(StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern))) === false) { $result['flags'] |= self::PATTERN_ENDSWITH; } $result['pattern'] = $pattern; return $result; }
/** * Inserts file chunk. * @param string $data chunk binary content. */ private function insertChunk($data) { $chunkDocument = ['files_id' => $this->documentId, 'n' => $this->chunkCount, 'data' => new Binary($data, Binary::TYPE_GENERIC)]; hash_update($this->hashContext, $data); $this->collection->getChunkCollection()->insert($chunkDocument); $this->length += StringHelper::byteLength($data); $this->chunkCount++; }
/** * Sends the specified content as a file to the browser. * * Note that this method only prepares the response for file sending. The file is not sent * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. * * @param string $content the content to be sent. The existing [[content]] will be discarded. * @param string $attachmentName the file name shown to the user. * @param array $options additional options for sending the file. The following options are supported: * * - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'. * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, * meaning a download dialog will pop up. * * @return $this the response object itself * @throws HttpException if the requested range is not satisfiable * @see sendFile() for an example implementation. */ public function sendContentAsFile($content, $attachmentName, $options = []) { $headers = $this->getHeaders(); $contentLength = StringHelper::byteLength($content); $range = $this->getHttpRange($contentLength); if ($range === false) { $headers->set('Content-Range', "bytes */{$contentLength}"); throw new HttpException(416, 'Requested range not satisfiable'); } list($begin, $end) = $range; if ($begin != 0 || $end != $contentLength - 1) { $this->setStatusCode(206); $headers->set('Content-Range', "bytes {$begin}-{$end}/{$contentLength}"); $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1); } else { $this->setStatusCode(200); $this->content = $content; } $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream'; $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1); $this->format = self::FORMAT_RAW; return $this; }
/** * Performs string comparison using timing attack resistant approach. * @see http://codereview.stackexchange.com/questions/13512 * @param string $expected string to compare. * @param string $actual user-supplied string. * @return bool whether strings are equal. */ public function compareString($expected, $actual) { $expected .= ""; $actual .= ""; $expectedLength = StringHelper::byteLength($expected); $actualLength = StringHelper::byteLength($actual); $diff = $expectedLength - $actualLength; for ($i = 0; $i < $actualLength; $i++) { $diff |= ord($actual[$i]) ^ ord($expected[$i % $expectedLength]); } return $diff === 0; }
/** * @inheritdoc */ public function behaviors() { return [['class' => TimestampBehavior::className(), 'attributes' => [self::EVENT_BEFORE_INSERT => ['created_at']]], ['class' => BlameableBehavior::className(), 'attributes' => [self::EVENT_BEFORE_INSERT => ['created_by']]], ['class' => AttributeBehavior::className(), 'attributes' => [self::EVENT_BEFORE_INSERT => 'character_count', self::EVENT_BEFORE_UPDATE => 'character_count'], 'value' => function () { return StringHelper::byteLength($this->version_data); }]]; }
/** * The method internally called in [[self::parseInputValue()]] and parse only string typed value. * @see [[self::parseInputValue()]] * @param string $value * @return string|null */ protected function parseInputStringValue($value) { if ($value === '') { return null; } if (StringHelper::startsWith($value, $this->postParamUploadPrefix)) { $charset = Yii::$app->charset; $storagePrefixPos = mb_strpos($value, $this->postParamStoragePrefix, 0, $charset); $uploadPrefixLength = mb_strlen($this->postParamUploadPrefix, $charset); $valueLength = mb_strlen($value, $charset); $length = $storagePrefixPos === false ? $valueLength - $uploadPrefixLength : $storagePrefixPos - $uploadPrefixLength; $formName = mb_substr($value, $uploadPrefixLength, $length, $charset); if ($result = UploadedFile::getInstanceByName($formName)) { return $result; } if ($storagePrefixPos !== false) { $storagePrefixLength = mb_strlen($this->postParamStoragePrefix, $charset); $pos = $storagePrefixPos + $storagePrefixLength; $storageFileData = mb_substr($value, $pos, $valueLength - $pos, $charset); } } if (!isset($storageFileData) && StringHelper::startsWith($value, $this->postParamStoragePrefix)) { $storageFileData = StringHelper::byteSubstr($value, StringHelper::byteLength($this->postParamStoragePrefix)); } if (isset($storageFileData) && $this->getStorage()->fileExists($storageFileData)) { return $storageFileData; } return null; }
/** * Validates if the given data is tampered. * @param string $data the data to be validated. The data must be previously * generated by [[hashData()]]. * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" * function to see the supported hashing algorithms on your system. This must be the same * as the value passed to [[hashData()]] when generating the hash for the data. * @return string the real data with the hash stripped off. False if the data is tampered. * @see hashData() */ public static function validateData($data, $key, $algorithm = 'sha256') { $hashSize = StringHelper::byteLength(hash_hmac($algorithm, 'test', $key)); $n = StringHelper::byteLength($data); if ($n >= $hashSize) { $hash = StringHelper::byteSubstr($data, 0, $hashSize); $data2 = StringHelper::byteSubstr($data, $hashSize, $n - $hashSize); return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; } else { return false; } }
/** * Sends the specified content as a file to the browser. * * Note that this method only prepares the response for file sending. The file is not sent * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. * * @param string $content the content to be sent. The existing [[content]] will be discarded. * @param string $attachmentName the file name shown to the user. * @param string $mimeType the MIME type of the content. * @return static the response object itself * @throws HttpException if the requested range is not satisfiable */ public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream') { $headers = $this->getHeaders(); $contentLength = StringHelper::byteLength($content); $range = $this->getHttpRange($contentLength); if ($range === false) { $headers->set('Content-Range', "bytes */{$contentLength}"); throw new HttpException(416, 'Requested range not satisfiable'); } $headers->setDefault('Pragma', 'public')->setDefault('Accept-Ranges', 'bytes')->setDefault('Expires', '0')->setDefault('Content-Type', $mimeType)->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')->setDefault('Content-Transfer-Encoding', 'binary')->setDefault('Content-Length', StringHelper::byteLength($content))->setDefault('Content-Disposition', "attachment; filename=\"{$attachmentName}\""); list($begin, $end) = $range; if ($begin != 0 || $end != $contentLength - 1) { $this->setStatusCode(206); $headers->set('Content-Range', "bytes {$begin}-{$end}/{$contentLength}"); $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1); } else { $this->setStatusCode(200); $this->content = $content; } $this->format = self::FORMAT_RAW; return $this; }
/** * Builds a normalized key from a given primary key value. * * @param mixed $key the key to be normalized * @return string the generated key */ public static function buildKey($key) { if (is_numeric($key)) { return $key; } elseif (is_string($key)) { return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key); } elseif (is_array($key)) { if (count($key) == 1) { return self::buildKey(reset($key)); } ksort($key); // ensure order is always the same $isNumeric = true; foreach ($key as $value) { if (!is_numeric($value)) { $isNumeric = false; } } if ($isNumeric) { return implode('-', $key); } } return md5(json_encode($key, JSON_NUMERIC_CHECK)); }
/** * second pass, generate file * * @param string $fileName filename * * @return void * @since 1.0.0 */ public function generateImage($name, $tempName = null) { try { $tempFile = false; Yii::$app->getSession()->open(); $sessionId = Yii::$app->getRequest()->get('key', Yii::$app->getSession()->getId()); $id = Yii::$app->getRequest()->get('id', 'unk'); if (empty($tempName) === false) { $tempFile = true; $fileName = $tempName; $targetPath = Yii::getAlias(UploadedFile::$targetPath); } else { $fileName = $name; $targetPath = Yii::getAlias(Yii::$app->getRequest()->get('targetPathAlias', '@webroot')); } if ($tempFile === false) { $replacement = []; $rawTargetPathAlias = Yii::$app->getRequest()->get('targetPathAlias', '@webroot'); if (preg_match_all('/({[^}]+})/', $rawTargetPathAlias, $matches) > 0) { if (isset($matches[1]) === true) { foreach ($matches[1] as $repKey) { $replacement[$repKey] = Yii::$app->getRequest()->get($repKey, ''); } $targetPath = str_replace(array_keys($replacement), array_values($replacement), $targetPath); } } } $file = $targetPath . DIRECTORY_SEPARATOR . $fileName; if (is_file($file) === true) { $width = Yii::$app->getRequest()->get('width', $this->width); $height = Yii::$app->getRequest()->get('height', $this->height); $fit = Yii::$app->getRequest()->get('fit', $this->fit); if ($fit === 'true' || $fit === 1 || $fit === true) { $fit = true; } else { $fit = false; } //TODO: we should remove the bad @ $imageInfo = @getimagesize($file); $supportedTypes = [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG]; if ($imageInfo !== false && in_array($imageInfo[2], $supportedTypes) === true) { if ($tempFile === false) { $image = Image::create($file)->resize($width, $height)->setFit($fit); $imageContentType = $image->getContentType(); $imageData = file_get_contents($image->getUrl()); } else { try { $image = Image::create($file)->resize($width, $height)->setFit($fit); } catch (Exception $e) { Yii::error($e->getMessage(), __METHOD__); throw $e; } $imageContentType = $image->getContentType(); $imageData = $image->liveRender(); } } else { $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); //TODO:handle default image $imageName = Yii::getAlias('@sweelix/yii2/plupload/icons') . DIRECTORY_SEPARATOR . $ext . '.png'; if (file_exists($imageName) === false) { $imageName = Yii::getAlias('@sweelix/yii2/plupload/icons') . DIRECTORY_SEPARATOR . 'unk.png'; } $image = Image::create($imageName)->resize($width, $height)->setFit($fit); $imageContentType = $image->getContentType(); $imageData = file_get_contents($image->getUrl(true)); } } $response = Yii::$app->getResponse(); $headers = $response->getHeaders(); $headers->setDefault('Pragma', 'public')->setDefault('Expires', '0')->setDefault('Content-Type', $imageContentType)->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')->setDefault('Content-Transfer-Encoding', 'binary')->setDefault('Content-Length', StringHelper::byteLength($imageData)); $response->format = Response::FORMAT_RAW; $response->content = $imageData; return $response; } catch (Exception $e) { Yii::error($e->getMessage(), __METHOD__); throw $e; } }
/** * Writes to stream. * This method is called in response to `fwrite()`. * @see fwrite() * @param string $data string to be stored into the underlying stream. * @return int the number of bytes that were successfully stored. */ public function stream_write($data) { if ($this->upload === null) { return false; } $this->upload->addContent($data); $result = StringHelper::byteLength($data); $this->pointerOffset += $result; return $result; }
public function testStrlen() { $this->assertEquals(4, StringHelper::byteLength('this')); $this->assertEquals(6, StringHelper::byteLength('это')); }
/** * @inheritdoc */ public function parse($rawBody, $contentType) { if (!empty($_POST) || !empty($_FILES)) { // normal POST request is parsed by PHP automatically return $_POST; } if (empty($rawBody)) { return []; } if (!preg_match('/boundary=(.*)$/is', $contentType, $matches)) { return []; } $boundary = $matches[1]; $bodyParts = preg_split('/-+' . preg_quote($boundary) . '/s', $rawBody); array_pop($bodyParts); // last block always has no data, contains boundary ending like `--` $bodyParams = []; $filesCount = 0; foreach ($bodyParts as $bodyPart) { if (empty($bodyPart)) { continue; } list($headers, $value) = preg_split("/\\R\\R/", $bodyPart, 2); $headers = $this->parseHeaders($headers); if (!isset($headers['content-disposition']['name'])) { continue; } if (isset($headers['content-disposition']['filename'])) { // file upload: if ($filesCount >= $this->getUploadFileMaxCount()) { continue; } $fileInfo = ['name' => $headers['content-disposition']['filename'], 'type' => ArrayHelper::getValue($headers, 'content-type', 'application/octet-stream'), 'size' => StringHelper::byteLength($value), 'error' => UPLOAD_ERR_OK, 'tmp_name' => null]; if ($fileInfo['size'] > $this->getUploadFileMaxSize()) { $fileInfo['error'] = UPLOAD_ERR_INI_SIZE; } else { $tmpResource = tmpfile(); if ($tmpResource === false) { $fileInfo['error'] = UPLOAD_ERR_CANT_WRITE; } else { $tmpResourceMetaData = stream_get_meta_data($tmpResource); $tmpFileName = $tmpResourceMetaData['uri']; if (empty($tmpFileName)) { $fileInfo['error'] = UPLOAD_ERR_CANT_WRITE; @fclose($tmpResource); } else { fwrite($tmpResource, $value); $fileInfo['tmp_name'] = $tmpFileName; $fileInfo['tmp_resource'] = $tmpResource; // save file resource, otherwise it will be deleted } } } $this->addFile($_FILES, $headers['content-disposition']['name'], $fileInfo); $filesCount++; } else { // regular parameter: $this->addValue($bodyParams, $headers['content-disposition']['name'], $value); } } return $bodyParams; }
/** * Get full path to image by Uri * * @param string $url Uri * @return string */ public function getImageByUri($url) { $return = false; $url = (string) parse_url($url, PHP_URL_PATH); $file = StringHelper::basename($url, '.' . $this->config['extension']); if (32 > (int) StringHelper::byteLength($file)) { return $return; } $fileName = StringHelper::byteSubstr($file, 0, 32); $suffix = StringHelper::byteSubstr($file, 32); if ($result = $this->arcresultOne($fileName)) { $dirName = dirname($result->path); $targetPath = FileHelper::normalizePath(\Yii::getAlias($this->config['directories']['target'] . $dirName)); $sourceFile = $targetPath . DIRECTORY_SEPARATOR . $fileName . '.' . $this->config['extension']; if (is_file($sourceFile)) { $return = $sourceFile; } if (empty($suffix)) { return $return; } $itemData = ['extension' => $this->config['extension'], 'quality' => (int) $this->config['quality'], 'file' => $fileName, 'source' => $sourceFile, 'directories' => ['source' => $targetPath, 'target' => $targetPath], 'targets' => []]; if (false === is_file($itemData['source'])) { if ($files = glob($targetPath . DIRECTORY_SEPARATOR . $fileName . '*')) { $fileSize = 0; foreach ($files as $file) { if ($fileSize < filesize($file)) { $itemData['source'] = $file; $fileSize = filesize($file); } } } } if (is_file($itemData['source'])) { if (false === empty($this->config['targets'])) { foreach ($this->config['targets'] as $name => $target) { if (isset($target['suffix']) && $suffix === $target['suffix']) { $itemData['targets'][$name] = $target; break; } } } if (empty($itemData['targets'])) { if (false === empty($this->config['commands'])) { $status = false; foreach ($this->config['commands'] as $command) { if (false === empty($command['targets'])) { foreach ($command['targets'] as $name => $target) { if (isset($target['suffix']) && $suffix === $target['suffix']) { $itemData['targets'][$name] = $target; $status = true; break; } } } if ($status) { break; } } } } if ($this->makeFile($itemData)) { if (is_file($targetPath . DIRECTORY_SEPARATOR . basename($url))) { $return = $targetPath . DIRECTORY_SEPARATOR . basename($url); } } } } return $return; }
/** * Validates CSRF token * * @param string $token * @param string $trueToken * @return boolean */ private function validateCsrfTokenInternal($token, $trueToken) { $token = base64_decode(str_replace('.', '+', $token)); $n = StringHelper::byteLength($token); if ($n <= static::CSRF_MASK_LENGTH) { return false; } $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); $token = $this->xorTokens($mask, $token); return $token === $trueToken; }
/** * Checks if the given file path satisfies the filtering options. * @param string $path the path of the file or directory to be checked * @param array $options the filtering options. See [[findFiles()]] for explanations of * the supported options. * @return boolean whether the file or directory satisfies the filtering options. */ public static function filterPath($path, $options) { if (isset($options['filter'])) { $result = call_user_func($options['filter'], $path); if (is_bool($result)) { return $result; } } if (empty($options['except']) && empty($options['only'])) { return true; } $path = str_replace('\\', '/', $path); if ($isDir = is_dir($path)) { $path .= '/'; } $n = StringHelper::byteLength($path); if (!empty($options['except'])) { foreach ($options['except'] as $name) { if (StringHelper::byteSubstr($path, -StringHelper::byteLength($name), $n) === $name) { return false; } } } if (!$isDir && !empty($options['only'])) { foreach ($options['only'] as $name) { if (StringHelper::byteSubstr($path, -StringHelper::byteLength($name), $n) === $name) { return true; } } return false; } return true; }