initial commit
This commit is contained in:
697
vendor/symfony/browser-kit/AbstractBrowser.php
vendored
Normal file
697
vendor/symfony/browser-kit/AbstractBrowser.php
vendored
Normal file
@@ -0,0 +1,697 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\BadMethodCallException;
|
||||
use Symfony\Component\BrowserKit\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\BrowserKit\Exception\LogicException;
|
||||
use Symfony\Component\BrowserKit\Exception\RuntimeException;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\DomCrawler\Form;
|
||||
use Symfony\Component\DomCrawler\Link;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
|
||||
/**
|
||||
* Simulates a browser.
|
||||
*
|
||||
* To make the actual request, you need to implement the doRequest() method.
|
||||
*
|
||||
* If you want to be able to run requests in their own process (insulated flag),
|
||||
* you need to also implement the getScript() method.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @template TRequest of object
|
||||
* @template TResponse of object
|
||||
*/
|
||||
abstract class AbstractBrowser
|
||||
{
|
||||
protected History $history;
|
||||
protected CookieJar $cookieJar;
|
||||
protected array $server = [];
|
||||
protected Request $internalRequest;
|
||||
/** @psalm-var TRequest */
|
||||
protected object $request;
|
||||
protected Response $internalResponse;
|
||||
/** @psalm-var TResponse */
|
||||
protected object $response;
|
||||
protected Crawler $crawler;
|
||||
protected bool $useHtml5Parser = true;
|
||||
protected bool $insulated = false;
|
||||
protected ?string $redirect;
|
||||
protected bool $followRedirects = true;
|
||||
protected bool $followMetaRefresh = false;
|
||||
|
||||
private int $maxRedirects = -1;
|
||||
private int $redirectCount = 0;
|
||||
private array $redirects = [];
|
||||
private bool $isMainRequest = true;
|
||||
|
||||
/**
|
||||
* @param array $server The server parameters (equivalent of $_SERVER)
|
||||
*/
|
||||
public function __construct(array $server = [], ?History $history = null, ?CookieJar $cookieJar = null)
|
||||
{
|
||||
$this->setServerParameters($server);
|
||||
$this->history = $history ?? new History();
|
||||
$this->cookieJar = $cookieJar ?? new CookieJar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically follow redirects or not.
|
||||
*/
|
||||
public function followRedirects(bool $followRedirects = true): void
|
||||
{
|
||||
$this->followRedirects = $followRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically follow meta refresh redirects or not.
|
||||
*/
|
||||
public function followMetaRefresh(bool $followMetaRefresh = true): void
|
||||
{
|
||||
$this->followMetaRefresh = $followMetaRefresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether client automatically follows redirects or not.
|
||||
*/
|
||||
public function isFollowingRedirects(): bool
|
||||
{
|
||||
return $this->followRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of redirects that crawler can follow.
|
||||
*/
|
||||
public function setMaxRedirects(int $maxRedirects): void
|
||||
{
|
||||
$this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects;
|
||||
$this->followRedirects = -1 !== $this->maxRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of redirects that crawler can follow.
|
||||
*/
|
||||
public function getMaxRedirects(): int
|
||||
{
|
||||
return $this->maxRedirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insulated flag.
|
||||
*
|
||||
* @throws LogicException When Symfony Process Component is not installed
|
||||
*/
|
||||
public function insulate(bool $insulated = true): void
|
||||
{
|
||||
if ($insulated && !class_exists(\Symfony\Component\Process\Process::class)) {
|
||||
throw new LogicException('Unable to isolate requests as the Symfony Process Component is not installed. Try running "composer require symfony/process".');
|
||||
}
|
||||
|
||||
$this->insulated = $insulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets server parameters.
|
||||
*/
|
||||
public function setServerParameters(array $server): void
|
||||
{
|
||||
$this->server = array_merge([
|
||||
'HTTP_USER_AGENT' => 'Symfony BrowserKit',
|
||||
], $server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets single server parameter.
|
||||
*/
|
||||
public function setServerParameter(string $key, string $value): void
|
||||
{
|
||||
$this->server[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets single server parameter for specified key.
|
||||
*/
|
||||
public function getServerParameter(string $key, mixed $default = ''): mixed
|
||||
{
|
||||
return $this->server[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function xmlHttpRequest(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], ?string $content = null, bool $changeHistory = true): Crawler
|
||||
{
|
||||
$this->setServerParameter('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest');
|
||||
|
||||
try {
|
||||
return $this->request($method, $uri, $parameters, $files, $server, $content, $changeHistory);
|
||||
} finally {
|
||||
unset($this->server['HTTP_X_REQUESTED_WITH']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the request parameters into a JSON string and uses it as request content.
|
||||
*/
|
||||
public function jsonRequest(string $method, string $uri, array $parameters = [], array $server = [], bool $changeHistory = true): Crawler
|
||||
{
|
||||
$content = json_encode($parameters, \JSON_PRESERVE_ZERO_FRACTION);
|
||||
|
||||
$this->setServerParameter('CONTENT_TYPE', 'application/json');
|
||||
$this->setServerParameter('HTTP_ACCEPT', 'application/json');
|
||||
|
||||
try {
|
||||
return $this->request($method, $uri, [], [], $server, $content, $changeHistory);
|
||||
} finally {
|
||||
unset($this->server['CONTENT_TYPE']);
|
||||
unset($this->server['HTTP_ACCEPT']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the History instance.
|
||||
*/
|
||||
public function getHistory(): History
|
||||
{
|
||||
return $this->history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CookieJar instance.
|
||||
*/
|
||||
public function getCookieJar(): CookieJar
|
||||
{
|
||||
return $this->cookieJar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Crawler instance.
|
||||
*/
|
||||
public function getCrawler(): Crawler
|
||||
{
|
||||
return $this->crawler ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether parsing should be done using "masterminds/html5".
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useHtml5Parser(bool $useHtml5Parser): static
|
||||
{
|
||||
$this->useHtml5Parser = $useHtml5Parser;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BrowserKit Response instance.
|
||||
*/
|
||||
public function getInternalResponse(): Response
|
||||
{
|
||||
return $this->internalResponse ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current origin response instance.
|
||||
*
|
||||
* The origin response is the response instance that is returned
|
||||
* by the code that handles requests.
|
||||
*
|
||||
* @psalm-return TResponse
|
||||
*
|
||||
* @see doRequest()
|
||||
*/
|
||||
public function getResponse(): object
|
||||
{
|
||||
return $this->response ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BrowserKit Request instance.
|
||||
*/
|
||||
public function getInternalRequest(): Request
|
||||
{
|
||||
return $this->internalRequest ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current origin Request instance.
|
||||
*
|
||||
* The origin request is the request instance that is sent
|
||||
* to the code that handles requests.
|
||||
*
|
||||
* @psalm-return TRequest
|
||||
*
|
||||
* @see doRequest()
|
||||
*/
|
||||
public function getRequest(): object
|
||||
{
|
||||
return $this->request ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on a given link.
|
||||
*
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*/
|
||||
public function click(Link $link, array $serverParameters = []): Crawler
|
||||
{
|
||||
if ($link instanceof Form) {
|
||||
return $this->submit($link, [], $serverParameters);
|
||||
}
|
||||
|
||||
return $this->request($link->getMethod(), $link->getUri(), [], [], $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the first link (or clickable image) that contains the given text.
|
||||
*
|
||||
* @param string $linkText The text of the link or the alt attribute of the clickable image
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*/
|
||||
public function clickLink(string $linkText, array $serverParameters = []): Crawler
|
||||
{
|
||||
$crawler = $this->crawler ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
|
||||
return $this->click($crawler->selectLink($linkText)->link(), $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a form.
|
||||
*
|
||||
* @param array $values An array of form field values
|
||||
* @param array $serverParameters An array of server parameters
|
||||
*/
|
||||
public function submit(Form $form, array $values = [], array $serverParameters = []): Crawler
|
||||
{
|
||||
$form->setValues($values);
|
||||
|
||||
return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles(), $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first form that contains a button with the given content and
|
||||
* uses it to submit the given form field values.
|
||||
*
|
||||
* @param string $button The text content, id, value or name of the form <button> or <input type="submit">
|
||||
* @param array $fieldValues Use this syntax: ['my_form[name]' => '...', 'my_form[email]' => '...']
|
||||
* @param string $method The HTTP method used to submit the form
|
||||
* @param array $serverParameters These values override the ones stored in $_SERVER (HTTP headers must include an HTTP_ prefix as PHP does)
|
||||
*/
|
||||
public function submitForm(string $button, array $fieldValues = [], string $method = 'POST', array $serverParameters = []): Crawler
|
||||
{
|
||||
$crawler = $this->crawler ?? throw new BadMethodCallException(\sprintf('The "request()" method must be called before "%s()".', __METHOD__));
|
||||
$buttonNode = $crawler->selectButton($button);
|
||||
|
||||
if (0 === $buttonNode->count()) {
|
||||
throw new InvalidArgumentException(\sprintf('There is no button with "%s" as its content, id, value or name.', $button));
|
||||
}
|
||||
|
||||
$form = $buttonNode->form($fieldValues, $method);
|
||||
|
||||
return $this->submit($form, [], $serverParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a URI.
|
||||
*
|
||||
* @param string $method The request method
|
||||
* @param string $uri The URI to fetch
|
||||
* @param array $parameters The Request parameters
|
||||
* @param array $files The files
|
||||
* @param array $server The server parameters (HTTP headers are referenced with an HTTP_ prefix as PHP does)
|
||||
* @param string $content The raw body data
|
||||
* @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*/
|
||||
public function request(string $method, string $uri, array $parameters = [], array $files = [], array $server = [], ?string $content = null, bool $changeHistory = true): Crawler
|
||||
{
|
||||
if ($this->isMainRequest) {
|
||||
$this->redirectCount = 0;
|
||||
} else {
|
||||
++$this->redirectCount;
|
||||
}
|
||||
|
||||
$originalUri = $uri;
|
||||
|
||||
$uri = $this->getAbsoluteUri($uri);
|
||||
|
||||
$server = array_merge($this->server, $server);
|
||||
|
||||
if (!empty($server['HTTP_HOST']) && !parse_url($originalUri, \PHP_URL_HOST)) {
|
||||
$uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri);
|
||||
}
|
||||
|
||||
if (isset($server['HTTPS']) && !parse_url($originalUri, \PHP_URL_SCHEME)) {
|
||||
$uri = preg_replace('{^'.parse_url($uri, \PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri);
|
||||
}
|
||||
|
||||
if (!isset($server['HTTP_REFERER']) && !$this->history->isEmpty()) {
|
||||
$server['HTTP_REFERER'] = $this->history->current()->getUri();
|
||||
}
|
||||
|
||||
if (empty($server['HTTP_HOST'])) {
|
||||
$server['HTTP_HOST'] = $this->extractHost($uri);
|
||||
}
|
||||
|
||||
$server['HTTPS'] = 'https' === parse_url($uri, \PHP_URL_SCHEME);
|
||||
|
||||
$this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
|
||||
|
||||
$this->request = $this->filterRequest($this->internalRequest);
|
||||
|
||||
if (true === $changeHistory) {
|
||||
$this->history->add($this->internalRequest);
|
||||
}
|
||||
|
||||
if ($this->insulated) {
|
||||
$this->response = $this->doRequestInProcess($this->request);
|
||||
} else {
|
||||
$this->response = $this->doRequest($this->request);
|
||||
}
|
||||
|
||||
$this->internalResponse = $this->filterResponse($this->response);
|
||||
|
||||
$this->cookieJar->updateFromResponse($this->internalResponse, $uri);
|
||||
|
||||
$status = $this->internalResponse->getStatusCode();
|
||||
|
||||
if ($status >= 300 && $status < 400) {
|
||||
$this->redirect = $this->internalResponse->getHeader('Location');
|
||||
} else {
|
||||
$this->redirect = null;
|
||||
}
|
||||
|
||||
if ($this->followRedirects && $this->redirect) {
|
||||
$this->redirects[serialize($this->history->current())] = true;
|
||||
|
||||
return $this->crawler = $this->followRedirect();
|
||||
}
|
||||
|
||||
$this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type') ?? '');
|
||||
|
||||
// Check for meta refresh redirect
|
||||
if ($this->followMetaRefresh && null !== $redirect = $this->getMetaRefreshUrl()) {
|
||||
$this->redirect = $redirect;
|
||||
$this->redirects[serialize($this->history->current())] = true;
|
||||
$this->crawler = $this->followRedirect();
|
||||
}
|
||||
|
||||
return $this->crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request in another process.
|
||||
*
|
||||
* @psalm-param TRequest $request
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @psalm-return TResponse
|
||||
*
|
||||
* @throws \RuntimeException When processing returns exit code
|
||||
*/
|
||||
protected function doRequestInProcess(object $request)
|
||||
{
|
||||
$deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec');
|
||||
putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile);
|
||||
$_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile;
|
||||
$process = new PhpProcess($this->getScript($request), null, null);
|
||||
$process->run();
|
||||
|
||||
if (file_exists($deprecationsFile)) {
|
||||
$deprecations = file_get_contents($deprecationsFile);
|
||||
unlink($deprecationsFile);
|
||||
foreach ($deprecations ? unserialize($deprecations) : [] as $deprecation) {
|
||||
if ($deprecation[0]) {
|
||||
// unsilenced on purpose
|
||||
trigger_error($deprecation[1], \E_USER_DEPRECATED);
|
||||
} else {
|
||||
@trigger_error($deprecation[1], \E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
|
||||
throw new RuntimeException(\sprintf('OUTPUT: %s ERROR OUTPUT: %s.', $process->getOutput(), $process->getErrorOutput()));
|
||||
}
|
||||
|
||||
return unserialize($process->getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request.
|
||||
*
|
||||
* @psalm-param TRequest $request
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @psalm-return TResponse
|
||||
*/
|
||||
abstract protected function doRequest(object $request);
|
||||
|
||||
/**
|
||||
* Returns the script to execute when the request must be insulated.
|
||||
*
|
||||
* @param object $request An origin request instance
|
||||
*
|
||||
* @psalm-param TRequest $request
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws LogicException When this abstract class is not implemented
|
||||
*/
|
||||
protected function getScript(object $request)
|
||||
{
|
||||
throw new LogicException('To insulate requests, you need to override the getScript() method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the BrowserKit request to the origin one.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @psalm-return TRequest
|
||||
*/
|
||||
protected function filterRequest(Request $request)
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the origin response to the BrowserKit one.
|
||||
*
|
||||
* @psalm-param TResponse $response
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function filterResponse(object $response)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crawler.
|
||||
*
|
||||
* This method returns null if the DomCrawler component is not available.
|
||||
*/
|
||||
protected function createCrawlerFromContent(string $uri, string $content, string $type): ?Crawler
|
||||
{
|
||||
if (!class_exists(Crawler::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$crawler = new Crawler(null, $uri, null, $this->useHtml5Parser);
|
||||
$crawler->addContent($content, $type);
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the browser history.
|
||||
*/
|
||||
public function back(): Crawler
|
||||
{
|
||||
do {
|
||||
$request = $this->history->back();
|
||||
} while (\array_key_exists(serialize($request), $this->redirects));
|
||||
|
||||
return $this->requestFromRequest($request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in the browser history.
|
||||
*/
|
||||
public function forward(): Crawler
|
||||
{
|
||||
do {
|
||||
$request = $this->history->forward();
|
||||
} while (\array_key_exists(serialize($request), $this->redirects));
|
||||
|
||||
return $this->requestFromRequest($request, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current browser.
|
||||
*/
|
||||
public function reload(): Crawler
|
||||
{
|
||||
return $this->requestFromRequest($this->history->current(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow redirects?
|
||||
*
|
||||
* @throws LogicException If request was not a redirect
|
||||
*/
|
||||
public function followRedirect(): Crawler
|
||||
{
|
||||
if (!isset($this->redirect)) {
|
||||
throw new LogicException('The request was not redirected.');
|
||||
}
|
||||
|
||||
if (-1 !== $this->maxRedirects) {
|
||||
if ($this->redirectCount > $this->maxRedirects) {
|
||||
$this->redirectCount = 0;
|
||||
throw new LogicException(\sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects));
|
||||
}
|
||||
}
|
||||
|
||||
$request = $this->internalRequest;
|
||||
|
||||
if (\in_array($this->internalResponse->getStatusCode(), [301, 302, 303])) {
|
||||
$method = 'GET';
|
||||
$files = [];
|
||||
$content = null;
|
||||
} else {
|
||||
$method = $request->getMethod();
|
||||
$files = $request->getFiles();
|
||||
$content = $request->getContent();
|
||||
}
|
||||
|
||||
if ('GET' === strtoupper($method)) {
|
||||
// Don't forward parameters for GET request as it should reach the redirection URI
|
||||
$parameters = [];
|
||||
} else {
|
||||
$parameters = $request->getParameters();
|
||||
}
|
||||
|
||||
$server = $request->getServer();
|
||||
$server = $this->updateServerFromUri($server, $this->redirect);
|
||||
|
||||
$this->isMainRequest = false;
|
||||
|
||||
$response = $this->request($method, $this->redirect, $parameters, $files, $server, $content);
|
||||
|
||||
$this->isMainRequest = true;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://dev.w3.org/html5/spec-preview/the-meta-element.html#attr-meta-http-equiv-refresh
|
||||
*/
|
||||
private function getMetaRefreshUrl(): ?string
|
||||
{
|
||||
$metaRefresh = $this->getCrawler()->filter('head meta[http-equiv="refresh"]');
|
||||
foreach ($metaRefresh->extract(['content']) as $content) {
|
||||
if (preg_match('/^\s*0\s*;\s*URL\s*=\s*(?|\'([^\']++)|"([^"]++)|([^\'"].*))/i', $content, $m)) {
|
||||
return str_replace("\t\r\n", '', rtrim($m[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the client.
|
||||
*
|
||||
* It flushes history and all cookies.
|
||||
*/
|
||||
public function restart(): void
|
||||
{
|
||||
$this->cookieJar->clear();
|
||||
$this->history->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a URI and converts it to absolute if it is not already absolute.
|
||||
*/
|
||||
protected function getAbsoluteUri(string $uri): string
|
||||
{
|
||||
// already absolute?
|
||||
if (str_starts_with($uri, 'http://') || str_starts_with($uri, 'https://')) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
if (!$this->history->isEmpty()) {
|
||||
$currentUri = $this->history->current()->getUri();
|
||||
} else {
|
||||
$currentUri = \sprintf('http%s://%s/',
|
||||
isset($this->server['HTTPS']) ? 's' : '',
|
||||
$this->server['HTTP_HOST'] ?? 'localhost'
|
||||
);
|
||||
}
|
||||
|
||||
// protocol relative URL
|
||||
if ('' !== trim($uri, '/') && str_starts_with($uri, '//')) {
|
||||
return parse_url($currentUri, \PHP_URL_SCHEME).':'.$uri;
|
||||
}
|
||||
|
||||
// anchor or query string parameters?
|
||||
if (!$uri || '#' === $uri[0] || '?' === $uri[0]) {
|
||||
return preg_replace('/[#?].*?$/', '', $currentUri).$uri;
|
||||
}
|
||||
|
||||
if ('/' !== $uri[0]) {
|
||||
$path = parse_url($currentUri, \PHP_URL_PATH);
|
||||
|
||||
if (!str_ends_with($path, '/')) {
|
||||
$path = substr($path, 0, strrpos($path, '/') + 1);
|
||||
}
|
||||
|
||||
$uri = $path.$uri;
|
||||
}
|
||||
|
||||
return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request from a Request object directly.
|
||||
*
|
||||
* @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
||||
*/
|
||||
protected function requestFromRequest(Request $request, bool $changeHistory = true): Crawler
|
||||
{
|
||||
return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
|
||||
}
|
||||
|
||||
private function updateServerFromUri(array $server, string $uri): array
|
||||
{
|
||||
$server['HTTP_HOST'] = $this->extractHost($uri);
|
||||
$scheme = parse_url($uri, \PHP_URL_SCHEME);
|
||||
$server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' === $scheme;
|
||||
unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
private function extractHost(string $uri): ?string
|
||||
{
|
||||
$host = parse_url($uri, \PHP_URL_HOST);
|
||||
|
||||
if ($port = parse_url($uri, \PHP_URL_PORT)) {
|
||||
return $host.':'.$port;
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
78
vendor/symfony/browser-kit/CHANGELOG.md
vendored
Normal file
78
vendor/symfony/browser-kit/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* Add argument `$serverParameters` to `AbstractBrowser::click()` and `AbstractBrowser::clickLink()`
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Add `AbstractBrowser::useHtml5Parser()`
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* Add `toArray` method to `Response`
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Added `jsonRequest` method to `AbstractBrowser`
|
||||
* Allowed sending a body with GET requests when a content-type is defined
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] Request parameters are now casted to string in `Request::__construct()`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* Added PHPUnit constraints: `BrowserCookieValueSame` and `BrowserHasCookie`
|
||||
* Added `HttpBrowser`, an implementation of a browser with the HttpClient component
|
||||
* Renamed `Client` to `AbstractBrowser`
|
||||
* Marked `Response` final.
|
||||
* Deprecated `Response::buildHeader()`
|
||||
* Deprecated `Response::getStatus()`, use `Response::getStatusCode()` instead
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* The method `Client::submit()` will have a new `$serverParameters` argument
|
||||
in version 5.0, not defining it is deprecated
|
||||
* Added ability to read the "samesite" attribute of cookies using `Cookie::getSameSite()`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] Client will skip redirects during history navigation
|
||||
(back and forward calls) according to W3C Browsers recommendation
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] The request method is dropped from POST to GET when the response
|
||||
status code is 301.
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* Client HTTP user agent has been changed to 'Symfony BrowserKit'
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] `Client::followRedirect()` won't redirect responses with
|
||||
a non-3xx Status Code and `Location` header anymore, as per
|
||||
http://tools.ietf.org/html/rfc2616#section-14.30
|
||||
|
||||
* added `Client::getInternalRequest()` and `Client::getInternalResponse()` to
|
||||
have access to the BrowserKit internal request and response objects
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] The CookieJar internals have changed to allow cookies with the
|
||||
same name on different sub-domains/sub-paths
|
||||
299
vendor/symfony/browser-kit/Cookie.php
vendored
Normal file
299
vendor/symfony/browser-kit/Cookie.php
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\BrowserKit\Exception\UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Cookie represents an HTTP cookie.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Cookie
|
||||
{
|
||||
/**
|
||||
* Handles dates as defined by RFC 2616 section 3.3.1, and also some other
|
||||
* non-standard, but common formats.
|
||||
*/
|
||||
private const DATE_FORMATS = [
|
||||
'D, d M Y H:i:s T',
|
||||
'D, d-M-y H:i:s T',
|
||||
'D, d-M-Y H:i:s T',
|
||||
'D, d-m-y H:i:s T',
|
||||
'D, d-m-Y H:i:s T',
|
||||
'D M j G:i:s Y',
|
||||
'D M d H:i:s Y T',
|
||||
];
|
||||
|
||||
protected string $value;
|
||||
protected ?string $expires = null;
|
||||
protected string $path;
|
||||
protected string $rawValue;
|
||||
|
||||
/**
|
||||
* Sets a cookie.
|
||||
*
|
||||
* @param string $name The cookie name
|
||||
* @param string|null $value The value of the cookie
|
||||
* @param string|null $expires The time the cookie expires
|
||||
* @param string|null $path The path on the server in which the cookie will be available on
|
||||
* @param string $domain The domain that the cookie is available
|
||||
* @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
|
||||
* @param bool $httponly The cookie httponly flag
|
||||
* @param bool $encodedValue Whether the value is encoded or not
|
||||
* @param string|null $samesite The cookie samesite attribute
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
?string $value,
|
||||
?string $expires = null,
|
||||
?string $path = null,
|
||||
private string $domain = '',
|
||||
private bool $secure = false,
|
||||
private bool $httponly = true,
|
||||
bool $encodedValue = false,
|
||||
private ?string $samesite = null,
|
||||
) {
|
||||
if ($encodedValue) {
|
||||
$this->rawValue = $value ?? '';
|
||||
$this->value = urldecode($this->rawValue);
|
||||
} else {
|
||||
$this->value = $value ?? '';
|
||||
$this->rawValue = rawurlencode($this->value);
|
||||
}
|
||||
$this->path = $path ?: '/';
|
||||
|
||||
if (null !== $expires) {
|
||||
$timestampAsDateTime = \DateTimeImmutable::createFromFormat('U', $expires);
|
||||
if (false === $timestampAsDateTime) {
|
||||
throw new UnexpectedValueException(\sprintf('The cookie expiration time "%s" is not valid.', $expires));
|
||||
}
|
||||
|
||||
$this->expires = $timestampAsDateTime->format('U');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP representation of the Cookie.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$cookie = \sprintf('%s=%s', $this->name, $this->rawValue);
|
||||
|
||||
if (null !== $this->expires) {
|
||||
$dateTime = \DateTimeImmutable::createFromFormat('U', $this->expires, new \DateTimeZone('GMT'));
|
||||
$cookie .= '; expires='.str_replace('+0000', '', $dateTime->format(self::DATE_FORMATS[0]));
|
||||
}
|
||||
|
||||
if ('' !== $this->domain) {
|
||||
$cookie .= '; domain='.$this->domain;
|
||||
}
|
||||
|
||||
if ($this->path) {
|
||||
$cookie .= '; path='.$this->path;
|
||||
}
|
||||
|
||||
if ($this->secure) {
|
||||
$cookie .= '; secure';
|
||||
}
|
||||
|
||||
if ($this->httponly) {
|
||||
$cookie .= '; httponly';
|
||||
}
|
||||
|
||||
if (null !== $this->samesite) {
|
||||
$cookie .= '; samesite='.$this->samesite;
|
||||
}
|
||||
|
||||
return $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Cookie instance from a Set-Cookie header value.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $cookie, ?string $url = null): static
|
||||
{
|
||||
$parts = explode(';', $cookie);
|
||||
|
||||
if (!str_contains($parts[0], '=')) {
|
||||
throw new InvalidArgumentException(\sprintf('The cookie string "%s" is not valid.', $parts[0]));
|
||||
}
|
||||
|
||||
[$name, $value] = explode('=', array_shift($parts), 2);
|
||||
|
||||
$values = [
|
||||
'name' => trim($name),
|
||||
'value' => trim($value),
|
||||
'expires' => null,
|
||||
'path' => '/',
|
||||
'domain' => '',
|
||||
'secure' => false,
|
||||
'httponly' => false,
|
||||
'passedRawValue' => true,
|
||||
'samesite' => null,
|
||||
];
|
||||
|
||||
if (null !== $url) {
|
||||
if (false === ($urlParts = parse_url($url)) || !isset($urlParts['host'])) {
|
||||
throw new InvalidArgumentException(\sprintf('The URL "%s" is not valid.', $url));
|
||||
}
|
||||
|
||||
$values['domain'] = $urlParts['host'];
|
||||
$values['path'] = isset($urlParts['path']) ? substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')) : '';
|
||||
}
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
|
||||
if ('secure' === strtolower($part)) {
|
||||
// Ignore the secure flag if the original URI is not given or is not HTTPS
|
||||
if (null === $url || !isset($urlParts['scheme']) || 'https' !== $urlParts['scheme']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values['secure'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('httponly' === strtolower($part)) {
|
||||
$values['httponly'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (2 === \count($elements = explode('=', $part, 2))) {
|
||||
if ('expires' === strtolower($elements[0])) {
|
||||
$elements[1] = self::parseDate($elements[1]);
|
||||
}
|
||||
|
||||
$values[strtolower($elements[0])] = $elements[1];
|
||||
}
|
||||
}
|
||||
|
||||
return new static(
|
||||
$values['name'],
|
||||
$values['value'],
|
||||
$values['expires'],
|
||||
$values['path'],
|
||||
$values['domain'],
|
||||
$values['secure'],
|
||||
$values['httponly'],
|
||||
$values['passedRawValue'],
|
||||
$values['samesite']
|
||||
);
|
||||
}
|
||||
|
||||
private static function parseDate(string $dateValue): ?string
|
||||
{
|
||||
// trim single quotes around date if present
|
||||
if (($length = \strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length - 1]) {
|
||||
$dateValue = substr($dateValue, 1, -1);
|
||||
}
|
||||
|
||||
foreach (self::DATE_FORMATS as $dateFormat) {
|
||||
if (false !== $date = \DateTimeImmutable::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) {
|
||||
return $date->format('U');
|
||||
}
|
||||
}
|
||||
|
||||
// attempt a fallback for unusual formatting
|
||||
if (false !== $date = date_create_immutable($dateValue, new \DateTimeZone('GMT'))) {
|
||||
return $date->format('U');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the cookie.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the cookie.
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw value of the cookie.
|
||||
*/
|
||||
public function getRawValue(): string
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expires time of the cookie.
|
||||
*/
|
||||
public function getExpiresTime(): ?string
|
||||
{
|
||||
return $this->expires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the cookie.
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the domain of the cookie.
|
||||
*/
|
||||
public function getDomain(): string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secure flag of the cookie.
|
||||
*/
|
||||
public function isSecure(): bool
|
||||
{
|
||||
return $this->secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the httponly flag of the cookie.
|
||||
*/
|
||||
public function isHttpOnly(): bool
|
||||
{
|
||||
return $this->httponly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cookie has expired.
|
||||
*/
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return null !== $this->expires && 0 != $this->expires && $this->expires <= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the samesite attribute of the cookie.
|
||||
*/
|
||||
public function getSameSite(): ?string
|
||||
{
|
||||
return $this->samesite;
|
||||
}
|
||||
}
|
||||
218
vendor/symfony/browser-kit/CookieJar.php
vendored
Normal file
218
vendor/symfony/browser-kit/CookieJar.php
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* CookieJar.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class CookieJar
|
||||
{
|
||||
protected array $cookieJar = [];
|
||||
|
||||
public function set(Cookie $cookie): void
|
||||
{
|
||||
$this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cookie by name.
|
||||
*
|
||||
* You should never use an empty domain, but if you do so,
|
||||
* this method returns the first cookie for the given name/path
|
||||
* (this behavior ensures a BC behavior with previous versions of
|
||||
* Symfony).
|
||||
*/
|
||||
public function get(string $name, string $path = '/', ?string $domain = null): ?Cookie
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
foreach ($this->cookieJar as $cookieDomain => $pathCookies) {
|
||||
if ($cookieDomain && $domain) {
|
||||
$cookieDomain = '.'.ltrim($cookieDomain, '.');
|
||||
if (!str_ends_with('.'.$domain, $cookieDomain)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pathCookies as $cookiePath => $namedCookies) {
|
||||
if (!str_starts_with($path, $cookiePath)) {
|
||||
continue;
|
||||
}
|
||||
if (isset($namedCookies[$name])) {
|
||||
return $namedCookies[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a cookie by name.
|
||||
*
|
||||
* You should never use an empty domain, but if you do so,
|
||||
* all cookies for the given name/path expire (this behavior
|
||||
* ensures a BC behavior with previous versions of Symfony).
|
||||
*/
|
||||
public function expire(string $name, ?string $path = '/', ?string $domain = null): void
|
||||
{
|
||||
$path ??= '/';
|
||||
|
||||
if (!$domain) {
|
||||
// an empty domain means any domain
|
||||
// this should never happen but it allows for a better BC
|
||||
$domains = array_keys($this->cookieJar);
|
||||
} else {
|
||||
$domains = [$domain];
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
unset($this->cookieJar[$domain][$path][$name]);
|
||||
|
||||
if (empty($this->cookieJar[$domain][$path])) {
|
||||
unset($this->cookieJar[$domain][$path]);
|
||||
|
||||
if (empty($this->cookieJar[$domain])) {
|
||||
unset($this->cookieJar[$domain]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the cookies from the jar.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->cookieJar = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cookie jar from a response Set-Cookie headers.
|
||||
*
|
||||
* @param string[] $setCookies Set-Cookie headers from an HTTP response
|
||||
*/
|
||||
public function updateFromSetCookie(array $setCookies, ?string $uri = null): void
|
||||
{
|
||||
$cookies = [];
|
||||
|
||||
foreach ($setCookies as $cookie) {
|
||||
foreach (explode(',', $cookie) as $i => $part) {
|
||||
if (0 === $i || preg_match('/^(?P<token>\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) {
|
||||
$cookies[] = ltrim($part);
|
||||
} else {
|
||||
$cookies[\count($cookies) - 1] .= ','.$part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
try {
|
||||
$this->set(Cookie::fromString($cookie, $uri));
|
||||
} catch (InvalidArgumentException) {
|
||||
// invalid cookies are just ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cookie jar from a Response object.
|
||||
*/
|
||||
public function updateFromResponse(Response $response, ?string $uri = null): void
|
||||
{
|
||||
$this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired cookies.
|
||||
*
|
||||
* @return Cookie[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
$flattenedCookies = [];
|
||||
foreach ($this->cookieJar as $path) {
|
||||
foreach ($path as $cookies) {
|
||||
foreach ($cookies as $cookie) {
|
||||
$flattenedCookies[] = $cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $flattenedCookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired cookie values for the given URI.
|
||||
*/
|
||||
public function allValues(string $uri, bool $returnsRawValue = false): array
|
||||
{
|
||||
$this->flushExpiredCookies();
|
||||
|
||||
$parts = array_replace(['path' => '/'], parse_url($uri));
|
||||
$cookies = [];
|
||||
foreach ($this->cookieJar as $domain => $pathCookies) {
|
||||
if ($domain) {
|
||||
$domain = '.'.ltrim($domain, '.');
|
||||
if (!str_ends_with('.'.$parts['host'], $domain)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pathCookies as $path => $namedCookies) {
|
||||
if (!str_starts_with($parts['path'], $path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($namedCookies as $cookie) {
|
||||
if ($cookie->isSecure() && 'https' !== $parts['scheme']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns not yet expired raw cookie values for the given URI.
|
||||
*/
|
||||
public function allRawValues(string $uri): array
|
||||
{
|
||||
return $this->allValues($uri, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all expired cookies.
|
||||
*/
|
||||
public function flushExpiredCookies(): void
|
||||
{
|
||||
foreach ($this->cookieJar as $domain => $pathCookies) {
|
||||
foreach ($pathCookies as $path => $namedCookies) {
|
||||
foreach ($namedCookies as $name => $cookie) {
|
||||
if ($cookie->isExpired()) {
|
||||
unset($this->cookieJar[$domain][$path][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/BadMethodCallException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/BadMethodCallException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
21
vendor/symfony/browser-kit/Exception/ExceptionInterface.php
vendored
Normal file
21
vendor/symfony/browser-kit/Exception/ExceptionInterface.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
/**
|
||||
* Base ExceptionInterface for the BrowserKit component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/InvalidArgumentException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/InvalidArgumentException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/JsonException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/JsonException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
class JsonException extends \JsonException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/LogicException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/LogicException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
class LogicException extends \LogicException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/RuntimeException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/RuntimeException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
16
vendor/symfony/browser-kit/Exception/UnexpectedValueException.php
vendored
Normal file
16
vendor/symfony/browser-kit/Exception/UnexpectedValueException.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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\BrowserKit\Exception;
|
||||
|
||||
class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
94
vendor/symfony/browser-kit/History.php
vendored
Normal file
94
vendor/symfony/browser-kit/History.php
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* History.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class History
|
||||
{
|
||||
protected array $stack = [];
|
||||
protected int $position = -1;
|
||||
|
||||
/**
|
||||
* Clears the history.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->stack = [];
|
||||
$this->position = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Request to the history.
|
||||
*/
|
||||
public function add(Request $request): void
|
||||
{
|
||||
$this->stack = \array_slice($this->stack, 0, $this->position + 1);
|
||||
$this->stack[] = clone $request;
|
||||
$this->position = \count($this->stack) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the history is empty.
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return 0 === \count($this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in the history.
|
||||
*
|
||||
* @throws LogicException if the stack is already on the first page
|
||||
*/
|
||||
public function back(): Request
|
||||
{
|
||||
if ($this->position < 1) {
|
||||
throw new LogicException('You are already on the first page.');
|
||||
}
|
||||
|
||||
return clone $this->stack[--$this->position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in the history.
|
||||
*
|
||||
* @throws LogicException if the stack is already on the last page
|
||||
*/
|
||||
public function forward(): Request
|
||||
{
|
||||
if ($this->position > \count($this->stack) - 2) {
|
||||
throw new LogicException('You are already on the last page.');
|
||||
}
|
||||
|
||||
return clone $this->stack[++$this->position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current element in the history.
|
||||
*
|
||||
* @throws LogicException if the stack is empty
|
||||
*/
|
||||
public function current(): Request
|
||||
{
|
||||
if (-1 === $this->position) {
|
||||
throw new LogicException('The page history is empty.');
|
||||
}
|
||||
|
||||
return clone $this->stack[$this->position];
|
||||
}
|
||||
}
|
||||
161
vendor/symfony/browser-kit/HttpBrowser.php
vendored
Normal file
161
vendor/symfony/browser-kit/HttpBrowser.php
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\LogicException;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* An implementation of a browser using the HttpClient component
|
||||
* to make real HTTP requests.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @template-extends AbstractBrowser<Request, Response>
|
||||
*/
|
||||
class HttpBrowser extends AbstractBrowser
|
||||
{
|
||||
private HttpClientInterface $client;
|
||||
|
||||
public function __construct(?HttpClientInterface $client = null, ?History $history = null, ?CookieJar $cookieJar = null)
|
||||
{
|
||||
if (!$client && !class_exists(HttpClient::class)) {
|
||||
throw new LogicException(\sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
|
||||
}
|
||||
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
|
||||
parent::__construct([], $history, $cookieJar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function doRequest(object $request): Response
|
||||
{
|
||||
$headers = $this->getHeaders($request);
|
||||
[$body, $extraHeaders] = $this->getBodyAndExtraHeaders($request, $headers);
|
||||
|
||||
$response = $this->client->request($request->getMethod(), $request->getUri(), [
|
||||
'headers' => array_merge($headers, $extraHeaders),
|
||||
'body' => $body,
|
||||
'max_redirects' => 0,
|
||||
]);
|
||||
|
||||
return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [$body, $headers]
|
||||
*/
|
||||
private function getBodyAndExtraHeaders(Request $request, array $headers): array
|
||||
{
|
||||
if (\in_array($request->getMethod(), ['GET', 'HEAD']) && !isset($headers['content-type'])) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
if (!class_exists(AbstractPart::class)) {
|
||||
throw new LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
|
||||
}
|
||||
|
||||
if (null !== $content = $request->getContent()) {
|
||||
if (isset($headers['content-type'])) {
|
||||
return [$content, []];
|
||||
}
|
||||
|
||||
$part = new TextPart($content, 'utf-8', 'plain', '8bit');
|
||||
|
||||
return [$part->bodyToString(), $part->getPreparedHeaders()->toArray()];
|
||||
}
|
||||
|
||||
$fields = $request->getParameters();
|
||||
|
||||
if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) {
|
||||
$part = new FormDataPart(array_replace_recursive($fields, $uploadedFiles));
|
||||
|
||||
return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()];
|
||||
}
|
||||
|
||||
if (!$fields) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
array_walk_recursive($fields, $caster = static function (&$v) use (&$caster) {
|
||||
if (\is_object($v)) {
|
||||
if ($vars = get_object_vars($v)) {
|
||||
array_walk_recursive($vars, $caster);
|
||||
$v = $vars;
|
||||
} elseif ($v instanceof \Stringable) {
|
||||
$v = (string) $v;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [http_build_query($fields, '', '&'), ['Content-Type' => 'application/x-www-form-urlencoded']];
|
||||
}
|
||||
|
||||
protected function getHeaders(Request $request): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($request->getServer() as $key => $value) {
|
||||
$key = strtolower(str_replace('_', '-', $key));
|
||||
$contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
|
||||
if (str_starts_with($key, 'http-')) {
|
||||
$headers[substr($key, 5)] = $value;
|
||||
} elseif (isset($contentHeaders[$key])) {
|
||||
// CONTENT_* are not prefixed with HTTP_
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
$cookies = [];
|
||||
foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
|
||||
$cookies[] = $name.'='.$value;
|
||||
}
|
||||
if ($cookies) {
|
||||
$headers['cookie'] = implode('; ', $cookies);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively go through the list. If the file has a tmp_name, convert it to a DataPart.
|
||||
* Keep the original hierarchy.
|
||||
*/
|
||||
private function getUploadedFiles(array $files): array
|
||||
{
|
||||
$uploadedFiles = [];
|
||||
foreach ($files as $name => $file) {
|
||||
if (!\is_array($file)) {
|
||||
return $uploadedFiles;
|
||||
}
|
||||
if (!isset($file['tmp_name'])) {
|
||||
$uploadedFiles[$name] = $this->getUploadedFiles($file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' === $file['tmp_name']) {
|
||||
$uploadedFiles[$name] = new DataPart('', '');
|
||||
continue;
|
||||
}
|
||||
|
||||
$uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
|
||||
}
|
||||
|
||||
return $uploadedFiles;
|
||||
}
|
||||
}
|
||||
19
vendor/symfony/browser-kit/LICENSE
vendored
Normal file
19
vendor/symfony/browser-kit/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
17
vendor/symfony/browser-kit/README.md
vendored
Normal file
17
vendor/symfony/browser-kit/README.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
BrowserKit Component
|
||||
====================
|
||||
|
||||
The BrowserKit component simulates the behavior of a web browser, allowing you
|
||||
to make requests, click on links and submit forms programmatically.
|
||||
|
||||
The component comes with a concrete implementation that uses the HttpClient
|
||||
component to make real HTTP requests.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/browser_kit/introduction.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
99
vendor/symfony/browser-kit/Request.php
vendored
Normal file
99
vendor/symfony/browser-kit/Request.php
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* @param string $uri The request URI
|
||||
* @param string $method The HTTP method request
|
||||
* @param array $parameters The request parameters
|
||||
* @param array $files An array of uploaded files
|
||||
* @param array $cookies An array of cookies
|
||||
* @param array $server An array of server parameters
|
||||
* @param string|null $content The raw body data
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $uri,
|
||||
protected string $method,
|
||||
protected array $parameters = [],
|
||||
protected array $files = [],
|
||||
protected array $cookies = [],
|
||||
protected array $server = [],
|
||||
protected ?string $content = null,
|
||||
) {
|
||||
array_walk_recursive($parameters, static function (&$value) {
|
||||
$value = (string) $value;
|
||||
});
|
||||
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI.
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request HTTP method.
|
||||
*/
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request parameters.
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request server files.
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request cookies.
|
||||
*/
|
||||
public function getCookies(): array
|
||||
{
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request server parameters.
|
||||
*/
|
||||
public function getServer(): array
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request raw body data.
|
||||
*/
|
||||
public function getContent(): ?string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
109
vendor/symfony/browser-kit/Response.php
vendored
Normal file
109
vendor/symfony/browser-kit/Response.php
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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\BrowserKit;
|
||||
|
||||
use Symfony\Component\BrowserKit\Exception\JsonException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class Response
|
||||
{
|
||||
private array $jsonData;
|
||||
|
||||
/**
|
||||
* The headers array is a set of key/value pairs. If a header is present multiple times
|
||||
* then the value is an array of all the values.
|
||||
*
|
||||
* @param string $content The content of the response
|
||||
* @param int $status The response status code (302 "Found" by default)
|
||||
* @param array $headers An array of headers
|
||||
*/
|
||||
public function __construct(
|
||||
private string $content = '',
|
||||
private int $status = 200,
|
||||
private array $headers = [],
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the response object to string containing all headers and the response content.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$headers = '';
|
||||
foreach ($this->headers as $name => $value) {
|
||||
if (\is_string($value)) {
|
||||
$headers .= \sprintf("%s: %s\n", $name, $value);
|
||||
} else {
|
||||
foreach ($value as $headerValue) {
|
||||
$headers .= \sprintf("%s: %s\n", $name, $headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $headers."\n".$this->content;
|
||||
}
|
||||
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array|null The first header value if $first is true, an array of values otherwise
|
||||
*/
|
||||
public function getHeader(string $header, bool $first = true): string|array|null
|
||||
{
|
||||
$normalizedHeader = str_replace('-', '_', strtolower($header));
|
||||
foreach ($this->headers as $key => $value) {
|
||||
if (str_replace('-', '_', strtolower($key)) === $normalizedHeader) {
|
||||
if ($first) {
|
||||
return \is_array($value) ? (\count($value) ? $value[0] : '') : $value;
|
||||
}
|
||||
|
||||
return \is_array($value) ? $value : [$value];
|
||||
}
|
||||
}
|
||||
|
||||
return $first ? null : [];
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
if (isset($this->jsonData)) {
|
||||
return $this->jsonData;
|
||||
}
|
||||
|
||||
try {
|
||||
$content = json_decode($this->content, true, flags: \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
throw new JsonException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if (!\is_array($content)) {
|
||||
throw new JsonException(\sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content)));
|
||||
}
|
||||
|
||||
return $this->jsonData = $content;
|
||||
}
|
||||
}
|
||||
62
vendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php
vendored
Normal file
62
vendor/symfony/browser-kit/Test/Constraint/BrowserCookieValueSame.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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\BrowserKit\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
|
||||
final class BrowserCookieValueSame extends Constraint
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $value,
|
||||
private bool $raw = false,
|
||||
private string $path = '/',
|
||||
private ?string $domain = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
$str = \sprintf('has cookie "%s"', $this->name);
|
||||
if ('/' !== $this->path) {
|
||||
$str .= \sprintf(' with path "%s"', $this->path);
|
||||
}
|
||||
if ($this->domain) {
|
||||
$str .= \sprintf(' for domain "%s"', $this->domain);
|
||||
}
|
||||
$str .= \sprintf(' with %svalue "%s"', $this->raw ? 'raw ' : '', $this->value);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function matches($browser): bool
|
||||
{
|
||||
$cookie = $browser->getCookieJar()->get($this->name, $this->path, $this->domain);
|
||||
if (!$cookie) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->value === ($this->raw ? $cookie->getRawValue() : $cookie->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function failureDescription($browser): string
|
||||
{
|
||||
return 'the Browser '.$this->toString();
|
||||
}
|
||||
}
|
||||
54
vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php
vendored
Normal file
54
vendor/symfony/browser-kit/Test/Constraint/BrowserHasCookie.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\BrowserKit\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
|
||||
final class BrowserHasCookie extends Constraint
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $path = '/',
|
||||
private ?string $domain = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
$str = \sprintf('has cookie "%s"', $this->name);
|
||||
if ('/' !== $this->path) {
|
||||
$str .= \sprintf(' with path "%s"', $this->path);
|
||||
}
|
||||
if ($this->domain) {
|
||||
$str .= \sprintf(' for domain "%s"', $this->domain);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function matches($browser): bool
|
||||
{
|
||||
return null !== $browser->getCookieJar()->get($this->name, $this->path, $this->domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractBrowser $browser
|
||||
*/
|
||||
protected function failureDescription($browser): string
|
||||
{
|
||||
return 'the Browser '.$this->toString();
|
||||
}
|
||||
}
|
||||
35
vendor/symfony/browser-kit/composer.json
vendored
Normal file
35
vendor/symfony/browser-kit/composer.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "symfony/browser-kit",
|
||||
"type": "library",
|
||||
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
|
||||
"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.2",
|
||||
"symfony/dom-crawler": "^6.4|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/css-selector": "^6.4|^7.0",
|
||||
"symfony/http-client": "^6.4|^7.0",
|
||||
"symfony/mime": "^6.4|^7.0",
|
||||
"symfony/process": "^6.4|^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\BrowserKit\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
Reference in New Issue
Block a user