mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-12 03:01:32 +00:00
resolved conflicts
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation;
|
||||
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* BinaryFileResponse represents an HTTP response delivering a file.
|
||||
*
|
||||
* @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
|
||||
* @author stealth35 <stealth35-php@live.fr>
|
||||
* @author Igor Wiedler <igor@wiedler.ch>
|
||||
* @author Jordan Alliot <jordan.alliot@gmail.com>
|
||||
* @author Sergey Linnik <linniksa@gmail.com>
|
||||
*/
|
||||
class BinaryFileResponse extends Response
|
||||
{
|
||||
protected static $trustXSendfileTypeHeader = false;
|
||||
|
||||
/**
|
||||
* @var File
|
||||
*/
|
||||
protected $file;
|
||||
protected $offset = 0;
|
||||
protected $maxlen = -1;
|
||||
protected $deleteFileAfterSend = false;
|
||||
protected $chunkSize = 16 * 1024;
|
||||
|
||||
/**
|
||||
* @param \SplFileInfo|string $file The file to stream
|
||||
* @param int $status The response status code (200 "OK" by default)
|
||||
* @param array $headers An array of response headers
|
||||
* @param bool $public Files are public by default
|
||||
* @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
|
||||
* @param bool $autoEtag Whether the ETag header should be automatically set
|
||||
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
|
||||
*/
|
||||
public function __construct(\SplFileInfo|string $file, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||
{
|
||||
parent::__construct(null, $status, $headers);
|
||||
|
||||
$this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
|
||||
|
||||
if ($public) {
|
||||
$this->setPublic();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file to stream.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function setFile(\SplFileInfo|string $file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static
|
||||
{
|
||||
if (!$file instanceof File) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$file = new File($file->getPathname());
|
||||
} else {
|
||||
$file = new File((string) $file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$file->isReadable()) {
|
||||
throw new FileException('File must be readable.');
|
||||
}
|
||||
|
||||
$this->file = $file;
|
||||
|
||||
if ($autoEtag) {
|
||||
$this->setAutoEtag();
|
||||
}
|
||||
|
||||
if ($autoLastModified) {
|
||||
$this->setAutoLastModified();
|
||||
}
|
||||
|
||||
if ($contentDisposition) {
|
||||
$this->setContentDisposition($contentDisposition);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file.
|
||||
*/
|
||||
public function getFile(): File
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the response stream chunk size.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setChunkSize(int $chunkSize): static
|
||||
{
|
||||
if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
|
||||
throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
|
||||
}
|
||||
|
||||
$this->chunkSize = $chunkSize;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically sets the Last-Modified header according the file modification date.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoLastModified(): static
|
||||
{
|
||||
$this->setLastModified(\DateTimeImmutable::createFromFormat('U', $this->file->getMTime()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically sets the ETag header according to the checksum of the file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoEtag(): static
|
||||
{
|
||||
$this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Content-Disposition header with the given filename.
|
||||
*
|
||||
* @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
|
||||
* @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
|
||||
* @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = ''): static
|
||||
{
|
||||
if ('' === $filename) {
|
||||
$filename = $this->file->getFilename();
|
||||
}
|
||||
|
||||
if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) {
|
||||
$encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
|
||||
|
||||
for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
|
||||
$char = mb_substr($filename, $i, 1, $encoding);
|
||||
|
||||
if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
|
||||
$filenameFallback .= '_';
|
||||
} else {
|
||||
$filenameFallback .= $char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
|
||||
$this->headers->set('Content-Disposition', $dispositionHeader);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function prepare(Request $request): static
|
||||
{
|
||||
if ($this->isInformational() || $this->isEmpty()) {
|
||||
parent::prepare($request);
|
||||
|
||||
$this->maxlen = 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->headers->has('Content-Type')) {
|
||||
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
|
||||
}
|
||||
|
||||
parent::prepare($request);
|
||||
|
||||
$this->offset = 0;
|
||||
$this->maxlen = -1;
|
||||
|
||||
if (false === $fileSize = $this->file->getSize()) {
|
||||
return $this;
|
||||
}
|
||||
$this->headers->remove('Transfer-Encoding');
|
||||
$this->headers->set('Content-Length', $fileSize);
|
||||
|
||||
if (!$this->headers->has('Accept-Ranges')) {
|
||||
// Only accept ranges on safe HTTP methods
|
||||
$this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none');
|
||||
}
|
||||
|
||||
if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
|
||||
// Use X-Sendfile, do not send any content.
|
||||
$type = $request->headers->get('X-Sendfile-Type');
|
||||
$path = $this->file->getRealPath();
|
||||
// Fall back to scheme://path for stream wrapped locations.
|
||||
if (false === $path) {
|
||||
$path = $this->file->getPathname();
|
||||
}
|
||||
if ('x-accel-redirect' === strtolower($type)) {
|
||||
// Do X-Accel-Mapping substitutions.
|
||||
// @link https://github.com/rack/rack/blob/main/lib/rack/sendfile.rb
|
||||
// @link https://mattbrictson.com/blog/accelerated-rails-downloads
|
||||
if (!$request->headers->has('X-Accel-Mapping')) {
|
||||
throw new \LogicException('The "X-Accel-Mapping" header must be set when "X-Sendfile-Type" is set to "X-Accel-Redirect".');
|
||||
}
|
||||
$parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping'), ',=');
|
||||
foreach ($parts as $part) {
|
||||
[$pathPrefix, $location] = $part;
|
||||
if (str_starts_with($path, $pathPrefix)) {
|
||||
$path = $location.substr($path, \strlen($pathPrefix));
|
||||
// Only set X-Accel-Redirect header if a valid URI can be produced
|
||||
// as nginx does not serve arbitrary file paths.
|
||||
$this->headers->set($type, $path);
|
||||
$this->maxlen = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->headers->set($type, $path);
|
||||
$this->maxlen = 0;
|
||||
}
|
||||
} elseif ($request->headers->has('Range') && $request->isMethod('GET')) {
|
||||
// Process the range headers.
|
||||
if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
|
||||
$range = $request->headers->get('Range');
|
||||
|
||||
if (str_starts_with($range, 'bytes=')) {
|
||||
[$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0];
|
||||
|
||||
$end = ('' === $end) ? $fileSize - 1 : (int) $end;
|
||||
|
||||
if ('' === $start) {
|
||||
$start = $fileSize - $end;
|
||||
$end = $fileSize - 1;
|
||||
} else {
|
||||
$start = (int) $start;
|
||||
}
|
||||
|
||||
if ($start <= $end) {
|
||||
$end = min($end, $fileSize - 1);
|
||||
if ($start < 0 || $start > $end) {
|
||||
$this->setStatusCode(416);
|
||||
$this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
|
||||
} elseif ($end - $start < $fileSize - 1) {
|
||||
$this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
|
||||
$this->offset = $start;
|
||||
|
||||
$this->setStatusCode(206);
|
||||
$this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
|
||||
$this->headers->set('Content-Length', $end - $start + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->isMethod('HEAD')) {
|
||||
$this->maxlen = 0;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function hasValidIfRangeHeader(?string $header): bool
|
||||
{
|
||||
if ($this->getEtag() === $header) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (null === $lastModified = $this->getLastModified()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
|
||||
}
|
||||
|
||||
public function sendContent(): static
|
||||
{
|
||||
try {
|
||||
if (!$this->isSuccessful()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (0 === $this->maxlen) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$out = fopen('php://output', 'w');
|
||||
$file = fopen($this->file->getPathname(), 'r');
|
||||
|
||||
ignore_user_abort(true);
|
||||
|
||||
if (0 !== $this->offset) {
|
||||
fseek($file, $this->offset);
|
||||
}
|
||||
|
||||
$length = $this->maxlen;
|
||||
while ($length && !feof($file)) {
|
||||
$read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length;
|
||||
|
||||
if (false === $data = fread($file, $read)) {
|
||||
break;
|
||||
}
|
||||
while ('' !== $data) {
|
||||
$read = fwrite($out, $data);
|
||||
if (false === $read || connection_aborted()) {
|
||||
break 2;
|
||||
}
|
||||
if (0 < $length) {
|
||||
$length -= $read;
|
||||
}
|
||||
$data = substr($data, $read);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
fclose($file);
|
||||
} finally {
|
||||
if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
|
||||
unlink($this->file->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \LogicException when the content is not null
|
||||
*/
|
||||
public function setContent(?string $content): static
|
||||
{
|
||||
if (null !== $content) {
|
||||
throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContent(): string|false
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trust X-Sendfile-Type header.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function trustXSendfileTypeHeader()
|
||||
{
|
||||
self::$trustXSendfileTypeHeader = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is set to true, the file will be unlinked after the request is sent
|
||||
* Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function deleteFileAfterSend(bool $shouldDelete = true): static
|
||||
{
|
||||
$this->deleteFileAfterSend = $shouldDelete;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation;
|
||||
|
||||
/**
|
||||
* HTTP header utility functions.
|
||||
*
|
||||
* @author Christian Schmidt <github@chsc.dk>
|
||||
*/
|
||||
class HeaderUtils
|
||||
{
|
||||
public const DISPOSITION_ATTACHMENT = 'attachment';
|
||||
public const DISPOSITION_INLINE = 'inline';
|
||||
|
||||
/**
|
||||
* This class should not be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits an HTTP header by one or more separators.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* HeaderUtils::split('da, en-gb;q=0.8', ',;')
|
||||
* // => ['da'], ['en-gb', 'q=0.8']]
|
||||
*
|
||||
* @param string $separators List of characters to split on, ordered by
|
||||
* precedence, e.g. ',', ';=', or ',;='
|
||||
*
|
||||
* @return array Nested array with as many levels as there are characters in
|
||||
* $separators
|
||||
*/
|
||||
public static function split(string $header, string $separators): array
|
||||
{
|
||||
if ('' === $separators) {
|
||||
throw new \InvalidArgumentException('At least one separator must be specified.');
|
||||
}
|
||||
|
||||
$quotedSeparators = preg_quote($separators, '/');
|
||||
|
||||
preg_match_all('
|
||||
/
|
||||
(?!\s)
|
||||
(?:
|
||||
# quoted-string
|
||||
"(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)
|
||||
|
|
||||
# token
|
||||
[^"'.$quotedSeparators.']+
|
||||
)+
|
||||
(?<!\s)
|
||||
|
|
||||
# separator
|
||||
\s*
|
||||
(?<separator>['.$quotedSeparators.'])
|
||||
\s*
|
||||
/x', trim($header), $matches, \PREG_SET_ORDER);
|
||||
|
||||
return self::groupParts($matches, $separators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines an array of arrays into one associative array.
|
||||
*
|
||||
* Each of the nested arrays should have one or two elements. The first
|
||||
* value will be used as the keys in the associative array, and the second
|
||||
* will be used as the values, or true if the nested array only contains one
|
||||
* element. Array keys are lowercased.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* HeaderUtils::combine([['foo', 'abc'], ['bar']])
|
||||
* // => ['foo' => 'abc', 'bar' => true]
|
||||
*/
|
||||
public static function combine(array $parts): array
|
||||
{
|
||||
$assoc = [];
|
||||
foreach ($parts as $part) {
|
||||
$name = strtolower($part[0]);
|
||||
$value = $part[1] ?? true;
|
||||
$assoc[$name] = $value;
|
||||
}
|
||||
|
||||
return $assoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins an associative array into a string for use in an HTTP header.
|
||||
*
|
||||
* The key and value of each entry are joined with '=', and all entries
|
||||
* are joined with the specified separator and an additional space (for
|
||||
* readability). Values are quoted if necessary.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',')
|
||||
* // => 'foo=abc, bar, baz="a b c"'
|
||||
*/
|
||||
public static function toString(array $assoc, string $separator): string
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($assoc as $name => $value) {
|
||||
if (true === $value) {
|
||||
$parts[] = $name;
|
||||
} else {
|
||||
$parts[] = $name.'='.self::quote($value);
|
||||
}
|
||||
}
|
||||
|
||||
return implode($separator.' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string as a quoted string, if necessary.
|
||||
*
|
||||
* If a string contains characters not allowed by the "token" construct in
|
||||
* the HTTP specification, it is backslash-escaped and enclosed in quotes
|
||||
* to match the "quoted-string" construct.
|
||||
*/
|
||||
public static function quote(string $s): string
|
||||
{
|
||||
if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {
|
||||
return $s;
|
||||
}
|
||||
|
||||
return '"'.addcslashes($s, '"\\"').'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a quoted string.
|
||||
*
|
||||
* If passed an unquoted string that matches the "token" construct (as
|
||||
* defined in the HTTP specification), it is passed through verbatim.
|
||||
*/
|
||||
public static function unquote(string $s): string
|
||||
{
|
||||
return preg_replace('/\\\\(.)|"/', '$1', $s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an HTTP Content-Disposition field-value.
|
||||
*
|
||||
* @param string $disposition One of "inline" or "attachment"
|
||||
* @param string $filename A unicode string
|
||||
* @param string $filenameFallback A string containing only ASCII characters that
|
||||
* is semantically equivalent to $filename. If the filename is already ASCII,
|
||||
* it can be omitted, or just copied from $filename
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @see RFC 6266
|
||||
*/
|
||||
public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string
|
||||
{
|
||||
if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
|
||||
throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
|
||||
}
|
||||
|
||||
if ('' === $filenameFallback) {
|
||||
$filenameFallback = $filename;
|
||||
}
|
||||
|
||||
// filenameFallback is not ASCII.
|
||||
if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
|
||||
throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
|
||||
}
|
||||
|
||||
// percent characters aren't safe in fallback.
|
||||
if (str_contains($filenameFallback, '%')) {
|
||||
throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
|
||||
}
|
||||
|
||||
// path separators aren't allowed in either.
|
||||
if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) {
|
||||
throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
|
||||
}
|
||||
|
||||
$params = ['filename' => $filenameFallback];
|
||||
if ($filename !== $filenameFallback) {
|
||||
$params['filename*'] = "utf-8''".rawurlencode($filename);
|
||||
}
|
||||
|
||||
return $disposition.'; '.self::toString($params, ';');
|
||||
}
|
||||
|
||||
/**
|
||||
* Like parse_str(), but preserves dots in variable names.
|
||||
*/
|
||||
public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array
|
||||
{
|
||||
$q = [];
|
||||
|
||||
foreach (explode($separator, $query) as $v) {
|
||||
if (false !== $i = strpos($v, "\0")) {
|
||||
$v = substr($v, 0, $i);
|
||||
}
|
||||
|
||||
if (false === $i = strpos($v, '=')) {
|
||||
$k = urldecode($v);
|
||||
$v = '';
|
||||
} else {
|
||||
$k = urldecode(substr($v, 0, $i));
|
||||
$v = substr($v, $i);
|
||||
}
|
||||
|
||||
if (false !== $i = strpos($k, "\0")) {
|
||||
$k = substr($k, 0, $i);
|
||||
}
|
||||
|
||||
$k = ltrim($k, ' ');
|
||||
|
||||
if ($ignoreBrackets) {
|
||||
$q[$k][] = urldecode(substr($v, 1));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false === $i = strpos($k, '[')) {
|
||||
$q[] = bin2hex($k).$v;
|
||||
} else {
|
||||
$q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ignoreBrackets) {
|
||||
return $q;
|
||||
}
|
||||
|
||||
parse_str(implode('&', $q), $q);
|
||||
|
||||
$query = [];
|
||||
|
||||
foreach ($q as $k => $v) {
|
||||
if (false !== $i = strpos($k, '_')) {
|
||||
$query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;
|
||||
} else {
|
||||
$query[hex2bin($k)] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private static function groupParts(array $matches, string $separators, bool $first = true): array
|
||||
{
|
||||
$separator = $separators[0];
|
||||
$separators = substr($separators, 1) ?: '';
|
||||
$i = 0;
|
||||
|
||||
if ('' === $separators && !$first) {
|
||||
$parts = [''];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (!$i && isset($match['separator'])) {
|
||||
$i = 1;
|
||||
$parts[1] = '';
|
||||
} else {
|
||||
$parts[$i] .= self::unquote($match[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
$partMatches = [];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (($match['separator'] ?? null) === $separator) {
|
||||
++$i;
|
||||
} else {
|
||||
$partMatches[$i][] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($partMatches as $matches) {
|
||||
if ('' === $separators && '' !== $unquoted = self::unquote($matches[0][0])) {
|
||||
$parts[] = $unquoted;
|
||||
} elseif ($groupedParts = self::groupParts($matches, $separators, false)) {
|
||||
$parts[] = $groupedParts;
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation;
|
||||
|
||||
/**
|
||||
* RedirectResponse represents an HTTP response doing a redirect.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RedirectResponse extends Response
|
||||
{
|
||||
protected $targetUrl;
|
||||
|
||||
/**
|
||||
* Creates a redirect response so that it conforms to the rules defined for a redirect status code.
|
||||
*
|
||||
* @param string $url The URL to redirect to. The URL should be a full URL, with schema etc.,
|
||||
* but practically every browser redirects on paths only as well
|
||||
* @param int $status The HTTP status code (302 "Found" by default)
|
||||
* @param array $headers The headers (Location is always set to the given URL)
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc2616#section-10.3
|
||||
*/
|
||||
public function __construct(string $url, int $status = 302, array $headers = [])
|
||||
{
|
||||
parent::__construct('', $status, $headers);
|
||||
|
||||
$this->setTargetUrl($url);
|
||||
|
||||
if (!$this->isRedirect()) {
|
||||
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
|
||||
}
|
||||
|
||||
if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
|
||||
$this->headers->remove('cache-control');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target URL.
|
||||
*/
|
||||
public function getTargetUrl(): string
|
||||
{
|
||||
return $this->targetUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redirect target of this response.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setTargetUrl(string $url): static
|
||||
{
|
||||
if ('' === $url) {
|
||||
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
|
||||
}
|
||||
|
||||
$this->targetUrl = $url;
|
||||
|
||||
$this->setContent(
|
||||
sprintf('<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
|
||||
|
||||
<title>Redirecting to %1$s</title>
|
||||
</head>
|
||||
<body>
|
||||
Redirecting to <a href="%1$s">%1$s</a>.
|
||||
</body>
|
||||
</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
|
||||
|
||||
$this->headers->set('Location', $url);
|
||||
$this->headers->set('Content-Type', 'text/html; charset=utf-8');
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Vendored
+238
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\Session\Storage;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
|
||||
|
||||
/**
|
||||
* MockArraySessionStorage mocks the session for unit tests.
|
||||
*
|
||||
* No PHP session is actually started since a session can be initialized
|
||||
* and shutdown only once per PHP execution cycle.
|
||||
*
|
||||
* When doing functional testing, you should use MockFileSessionStorage instead.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
* @author Drak <drak@zikula.org>
|
||||
*/
|
||||
class MockArraySessionStorage implements SessionStorageInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $started = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $closed = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* @var MetadataBag
|
||||
*/
|
||||
protected $metadataBag;
|
||||
|
||||
/**
|
||||
* @var array|SessionBagInterface[]
|
||||
*/
|
||||
protected $bags = [];
|
||||
|
||||
public function __construct(string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->setMetadataBag($metaBag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setSessionData(array $array)
|
||||
{
|
||||
$this->data = $array;
|
||||
}
|
||||
|
||||
public function start(): bool
|
||||
{
|
||||
if ($this->started) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty($this->id)) {
|
||||
$this->id = $this->generateId();
|
||||
}
|
||||
|
||||
$this->loadSession();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function regenerate(bool $destroy = false, ?int $lifetime = null): bool
|
||||
{
|
||||
if (!$this->started) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
$this->metadataBag->stampNew($lifetime);
|
||||
$this->id = $this->generateId();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setId(string $id)
|
||||
{
|
||||
if ($this->started) {
|
||||
throw new \LogicException('Cannot set session ID after the session has started.');
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if (!$this->started || $this->closed) {
|
||||
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed.');
|
||||
}
|
||||
// nothing to do since we don't persist the session data
|
||||
$this->closed = false;
|
||||
$this->started = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
// clear out the bags
|
||||
foreach ($this->bags as $bag) {
|
||||
$bag->clear();
|
||||
}
|
||||
|
||||
// clear out the session
|
||||
$this->data = [];
|
||||
|
||||
// reconnect the bags to the session
|
||||
$this->loadSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function registerBag(SessionBagInterface $bag)
|
||||
{
|
||||
$this->bags[$bag->getName()] = $bag;
|
||||
}
|
||||
|
||||
public function getBag(string $name): SessionBagInterface
|
||||
{
|
||||
if (!isset($this->bags[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
|
||||
}
|
||||
|
||||
if (!$this->started) {
|
||||
$this->start();
|
||||
}
|
||||
|
||||
return $this->bags[$name];
|
||||
}
|
||||
|
||||
public function isStarted(): bool
|
||||
{
|
||||
return $this->started;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setMetadataBag(?MetadataBag $bag = null)
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/http-foundation', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
$this->metadataBag = $bag ?? new MetadataBag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MetadataBag.
|
||||
*/
|
||||
public function getMetadataBag(): MetadataBag
|
||||
{
|
||||
return $this->metadataBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a session ID.
|
||||
*
|
||||
* This doesn't need to be particularly cryptographically secure since this is just
|
||||
* a mock.
|
||||
*/
|
||||
protected function generateId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function loadSession()
|
||||
{
|
||||
$bags = array_merge($this->bags, [$this->metadataBag]);
|
||||
|
||||
foreach ($bags as $bag) {
|
||||
$key = $bag->getStorageKey();
|
||||
$this->data[$key] ??= [];
|
||||
$bag->initialize($this->data[$key]);
|
||||
}
|
||||
|
||||
$this->started = true;
|
||||
$this->closed = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"type": "library",
|
||||
"description": "Defines an object-oriented layer for the HTTP specification",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-mbstring": "~1.1",
|
||||
"symfony/polyfill-php83": "^1.27"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/dbal": "^2.13.1|^3|^4",
|
||||
"predis/predis": "^1.1|^2.0",
|
||||
"symfony/cache": "^6.4.12|^7.1.5",
|
||||
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0",
|
||||
"symfony/mime": "^5.4|^6.0|^7.0",
|
||||
"symfony/expression-language": "^5.4|^6.0|^7.0",
|
||||
"symfony/rate-limiter": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/cache": "<6.4.12|>=7.0,<7.1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
Reference in New Issue
Block a user