first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Stream\StreamInterface;
abstract class AbstractMessage implements MessageInterface
{
/** @var array HTTP header collection */
private $headers = [];
/** @var array mapping a lowercase header name to its name over the wire */
private $headerNames = [];
/** @var StreamInterface Message body */
private $body;
/** @var string HTTP protocol version of the message */
private $protocolVersion = '1.1';
public function __toString()
{
return static::getStartLineAndHeaders($this)
. "\r\n\r\n" . $this->getBody();
}
public function getProtocolVersion()
{
return $this->protocolVersion;
}
public function getBody()
{
return $this->body;
}
public function setBody(StreamInterface $body = null)
{
if ($body === null) {
// Setting a null body will remove the body of the request
$this->removeHeader('Content-Length');
$this->removeHeader('Transfer-Encoding');
}
$this->body = $body;
}
public function addHeader($header, $value)
{
if (is_array($value)) {
$current = array_merge($this->getHeaderAsArray($header), $value);
} else {
$current = $this->getHeaderAsArray($header);
$current[] = (string) $value;
}
$this->setHeader($header, $current);
}
public function addHeaders(array $headers)
{
foreach ($headers as $name => $header) {
$this->addHeader($name, $header);
}
}
public function getHeader($header)
{
$name = strtolower($header);
return isset($this->headers[$name])
? implode(', ', $this->headers[$name])
: '';
}
public function getHeaderAsArray($header)
{
$name = strtolower($header);
return isset($this->headers[$name]) ? $this->headers[$name] : [];
}
public function getHeaders()
{
$headers = [];
foreach ($this->headers as $name => $values) {
$headers[$this->headerNames[$name]] = $values;
}
return $headers;
}
public function setHeader($header, $value)
{
$header = trim($header);
$name = strtolower($header);
$this->headerNames[$name] = $header;
if (is_array($value)) {
foreach ($value as &$v) {
$v = trim($v);
}
$this->headers[$name] = $value;
} else {
$this->headers[$name] = [trim($value)];
}
}
public function setHeaders(array $headers)
{
$this->headers = $this->headerNames = [];
foreach ($headers as $key => $value) {
$this->addHeader($key, $value);
}
}
public function hasHeader($header)
{
return isset($this->headers[strtolower($header)]);
}
public function removeHeader($header)
{
$name = strtolower($header);
unset($this->headers[$name], $this->headerNames[$name]);
}
/**
* Parse an array of header values containing ";" separated data into an
* array of associative arrays representing the header key value pair
* data of the header. When a parameter does not contain a value, but just
* contains a key, this function will inject a key with a '' string value.
*
* @param MessageInterface $message That contains the header
* @param string $header Header to retrieve from the message
*
* @return array Returns the parsed header values.
*/
public static function parseHeader(MessageInterface $message, $header)
{
static $trimmed = "\"' \n\t\r";
$params = $matches = [];
foreach (self::normalizeHeader($message, $header) as $val) {
$part = [];
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
$m = $matches[0];
if (isset($m[1])) {
$part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
} else {
$part[] = trim($m[0], $trimmed);
}
}
}
if ($part) {
$params[] = $part;
}
}
return $params;
}
/**
* Converts an array of header values that may contain comma separated
* headers into an array of headers with no comma separated values.
*
* @param MessageInterface $message That contains the header
* @param string $header Header to retrieve from the message
*
* @return array Returns the normalized header field values.
*/
public static function normalizeHeader(MessageInterface $message, $header)
{
$h = $message->getHeaderAsArray($header);
for ($i = 0, $total = count($h); $i < $total; $i++) {
if (strpos($h[$i], ',') === false) {
continue;
}
foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $h[$i]) as $v) {
$h[] = trim($v);
}
unset($h[$i]);
}
return $h;
}
/**
* Gets the start-line and headers of a message as a string
*
* @param MessageInterface $message
*
* @return string
*/
public static function getStartLineAndHeaders(MessageInterface $message)
{
return static::getStartLine($message)
. self::getHeadersAsString($message);
}
/**
* Gets the headers of a message as a string
*
* @param MessageInterface $message
*
* @return string
*/
public static function getHeadersAsString(MessageInterface $message)
{
$result = '';
foreach ($message->getHeaders() as $name => $values) {
$result .= "\r\n{$name}: " . implode(', ', $values);
}
return $result;
}
/**
* Gets the start line of a message
*
* @param MessageInterface $message
*
* @return string
* @throws \InvalidArgumentException
*/
public static function getStartLine(MessageInterface $message)
{
if ($message instanceof RequestInterface) {
return trim($message->getMethod() . ' '
. $message->getResource())
. ' HTTP/' . $message->getProtocolVersion();
} elseif ($message instanceof ResponseInterface) {
return 'HTTP/' . $message->getProtocolVersion() . ' '
. $message->getStatusCode() . ' '
. $message->getReasonPhrase();
} else {
throw new \InvalidArgumentException('Unknown message type');
}
}
/**
* Accepts and modifies the options provided to the message in the
* constructor.
*
* Can be overridden in subclasses as necessary.
*
* @param array $options Options array passed by reference.
*/
protected function handleOptions(array &$options)
{
if (isset($options['protocol_version'])) {
$this->protocolVersion = $options['protocol_version'];
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace GuzzleHttp\Message;
/**
* Applies headers to a request.
*
* This interface can be used with Guzzle streams to apply body specific
* headers to a request during the PREPARE_REQUEST priority of the before event
*
* NOTE: a body that implements this interface will prevent a default
* content-type from being added to a request during the before event. If you
* want a default content-type to be added, then it will need to be done
* manually (e.g., using {@see GuzzleHttp\Mimetypes}).
*/
interface AppliesHeadersInterface
{
/**
* Apply headers to a request appropriate for the current state of the
* object.
*
* @param RequestInterface $request Request
*/
public function applyRequestHeaders(RequestInterface $request);
}

View File

@@ -0,0 +1,158 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Ring\Future\MagicFutureTrait;
use GuzzleHttp\Ring\Future\FutureInterface;
use GuzzleHttp\Stream\StreamInterface;
/**
* Represents a response that has not been fulfilled.
*
* When created, you must provide a function that is used to dereference the
* future result and return it's value. The function has no arguments and MUST
* return an instance of a {@see GuzzleHttp\Message\ResponseInterface} object.
*
* You can optionally provide a function in the constructor that can be used to
* cancel the future from completing if possible. This function has no
* arguments and returns a boolean value representing whether or not the
* response could be cancelled.
*
* @property ResponseInterface $_value
*/
class FutureResponse implements ResponseInterface, FutureInterface
{
use MagicFutureTrait;
/**
* Returns a FutureResponse that wraps another future.
*
* @param FutureInterface $future Future to wrap with a new future
* @param callable $onFulfilled Invoked when the future fulfilled
* @param callable $onRejected Invoked when the future rejected
* @param callable $onProgress Invoked when the future progresses
*
* @return FutureResponse
*/
public static function proxy(
FutureInterface $future,
callable $onFulfilled = null,
callable $onRejected = null,
callable $onProgress = null
) {
return new FutureResponse(
$future->then($onFulfilled, $onRejected, $onProgress),
[$future, 'wait'],
[$future, 'cancel']
);
}
public function getStatusCode()
{
return $this->_value->getStatusCode();
}
public function setStatusCode($code)
{
$this->_value->setStatusCode($code);
}
public function getReasonPhrase()
{
return $this->_value->getReasonPhrase();
}
public function setReasonPhrase($phrase)
{
$this->_value->setReasonPhrase($phrase);
}
public function getEffectiveUrl()
{
return $this->_value->getEffectiveUrl();
}
public function setEffectiveUrl($url)
{
$this->_value->setEffectiveUrl($url);
}
public function json(array $config = [])
{
return $this->_value->json($config);
}
public function xml(array $config = [])
{
return $this->_value->xml($config);
}
public function __toString()
{
try {
return $this->_value->__toString();
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return '';
}
}
public function getProtocolVersion()
{
return $this->_value->getProtocolVersion();
}
public function setBody(StreamInterface $body = null)
{
$this->_value->setBody($body);
}
public function getBody()
{
return $this->_value->getBody();
}
public function getHeaders()
{
return $this->_value->getHeaders();
}
public function getHeader($header)
{
return $this->_value->getHeader($header);
}
public function getHeaderAsArray($header)
{
return $this->_value->getHeaderAsArray($header);
}
public function hasHeader($header)
{
return $this->_value->hasHeader($header);
}
public function removeHeader($header)
{
$this->_value->removeHeader($header);
}
public function addHeader($header, $value)
{
$this->_value->addHeader($header, $value);
}
public function addHeaders(array $headers)
{
$this->_value->addHeaders($headers);
}
public function setHeader($header, $value)
{
$this->_value->setHeader($header, $value);
}
public function setHeaders(array $headers)
{
$this->_value->setHeaders($headers);
}
}

View File

@@ -0,0 +1,364 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Event\ListenerAttacherTrait;
use GuzzleHttp\Post\PostBody;
use GuzzleHttp\Post\PostFile;
use GuzzleHttp\Post\PostFileInterface;
use GuzzleHttp\Query;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Subscriber\Cookie;
use GuzzleHttp\Subscriber\HttpError;
use GuzzleHttp\Subscriber\Redirect;
use GuzzleHttp\Url;
use \InvalidArgumentException as Iae;
/**
* Default HTTP request factory used to create Request and Response objects.
*/
class MessageFactory implements MessageFactoryInterface
{
use ListenerAttacherTrait;
/** @var HttpError */
private $errorPlugin;
/** @var Redirect */
private $redirectPlugin;
/** @var array */
private $customOptions;
/** @var array Request options passed through to request Config object */
private static $configMap = [
'connect_timeout' => 1, 'timeout' => 1, 'verify' => 1, 'ssl_key' => 1,
'cert' => 1, 'proxy' => 1, 'debug' => 1, 'save_to' => 1, 'stream' => 1,
'expect' => 1, 'future' => 1
];
/** @var array Default allow_redirects request option settings */
private static $defaultRedirect = [
'max' => 5,
'strict' => false,
'referer' => false,
'protocols' => ['http', 'https']
];
/**
* @param array $customOptions Associative array of custom request option
* names mapping to functions used to apply
* the option. The function accepts the request
* and the option value to apply.
*/
public function __construct(array $customOptions = [])
{
$this->errorPlugin = new HttpError();
$this->redirectPlugin = new Redirect();
$this->customOptions = $customOptions;
}
public function createResponse(
$statusCode,
array $headers = [],
$body = null,
array $options = []
) {
if (null !== $body) {
$body = Stream::factory($body);
}
return new Response($statusCode, $headers, $body, $options);
}
public function createRequest($method, $url, array $options = [])
{
// Handle the request protocol version option that needs to be
// specified in the request constructor.
if (isset($options['version'])) {
$options['config']['protocol_version'] = $options['version'];
unset($options['version']);
}
$request = new Request($method, $url, [], null,
isset($options['config']) ? $options['config'] : []);
unset($options['config']);
// Use a POST body by default
if (strtoupper($method) == 'POST'
&& !isset($options['body'])
&& !isset($options['json'])
) {
$options['body'] = [];
}
if ($options) {
$this->applyOptions($request, $options);
}
return $request;
}
/**
* Create a request or response object from an HTTP message string
*
* @param string $message Message to parse
*
* @return RequestInterface|ResponseInterface
* @throws \InvalidArgumentException if unable to parse a message
*/
public function fromMessage($message)
{
static $parser;
if (!$parser) {
$parser = new MessageParser();
}
// Parse a response
if (strtoupper(substr($message, 0, 4)) == 'HTTP') {
$data = $parser->parseResponse($message);
return $this->createResponse(
$data['code'],
$data['headers'],
$data['body'] === '' ? null : $data['body'],
$data
);
}
// Parse a request
if (!($data = ($parser->parseRequest($message)))) {
throw new \InvalidArgumentException('Unable to parse request');
}
return $this->createRequest(
$data['method'],
Url::buildUrl($data['request_url']),
[
'headers' => $data['headers'],
'body' => $data['body'] === '' ? null : $data['body'],
'config' => [
'protocol_version' => $data['protocol_version']
]
]
);
}
/**
* Apply POST fields and files to a request to attempt to give an accurate
* representation.
*
* @param RequestInterface $request Request to update
* @param array $body Body to apply
*/
protected function addPostData(RequestInterface $request, array $body)
{
static $fields = ['string' => true, 'array' => true, 'NULL' => true,
'boolean' => true, 'double' => true, 'integer' => true];
$post = new PostBody();
foreach ($body as $key => $value) {
if (isset($fields[gettype($value)])) {
$post->setField($key, $value);
} elseif ($value instanceof PostFileInterface) {
$post->addFile($value);
} else {
$post->addFile(new PostFile($key, $value));
}
}
if ($request->getHeader('Content-Type') == 'multipart/form-data') {
$post->forceMultipartUpload(true);
}
$request->setBody($post);
}
protected function applyOptions(
RequestInterface $request,
array $options = []
) {
$config = $request->getConfig();
$emitter = $request->getEmitter();
foreach ($options as $key => $value) {
if (isset(self::$configMap[$key])) {
$config[$key] = $value;
continue;
}
switch ($key) {
case 'allow_redirects':
if ($value === false) {
continue 2;
}
if ($value === true) {
$value = self::$defaultRedirect;
} elseif (!is_array($value)) {
throw new Iae('allow_redirects must be true, false, or array');
} else {
// Merge the default settings with the provided settings
$value += self::$defaultRedirect;
}
$config['redirect'] = $value;
$emitter->attach($this->redirectPlugin);
break;
case 'decode_content':
if ($value === false) {
continue 2;
}
$config['decode_content'] = true;
if ($value !== true) {
$request->setHeader('Accept-Encoding', $value);
}
break;
case 'headers':
if (!is_array($value)) {
throw new Iae('header value must be an array');
}
foreach ($value as $k => $v) {
$request->setHeader($k, $v);
}
break;
case 'exceptions':
if ($value === true) {
$emitter->attach($this->errorPlugin);
}
break;
case 'body':
if (is_array($value)) {
$this->addPostData($request, $value);
} elseif ($value !== null) {
$request->setBody(Stream::factory($value));
}
break;
case 'auth':
if (!$value) {
continue 2;
}
if (is_array($value)) {
$type = isset($value[2]) ? strtolower($value[2]) : 'basic';
} else {
$type = strtolower($value);
}
$config['auth'] = $value;
if ($type == 'basic') {
$request->setHeader(
'Authorization',
'Basic ' . base64_encode("$value[0]:$value[1]")
);
} elseif ($type == 'digest') {
// @todo: Do not rely on curl
$config->setPath('curl/' . CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
$config->setPath('curl/' . CURLOPT_USERPWD, "$value[0]:$value[1]");
}
break;
case 'query':
if ($value instanceof Query) {
$original = $request->getQuery();
// Do not overwrite existing query string variables by
// overwriting the object with the query string data passed
// in the URL
$value->overwriteWith($original->toArray());
$request->setQuery($value);
} elseif (is_array($value)) {
// Do not overwrite existing query string variables
$query = $request->getQuery();
foreach ($value as $k => $v) {
if (!isset($query[$k])) {
$query[$k] = $v;
}
}
} else {
throw new Iae('query must be an array or Query object');
}
break;
case 'cookies':
if ($value === true) {
static $cookie = null;
if (!$cookie) {
$cookie = new Cookie();
}
$emitter->attach($cookie);
} elseif (is_array($value)) {
$emitter->attach(
new Cookie(CookieJar::fromArray($value, $request->getHost()))
);
} elseif ($value instanceof CookieJarInterface) {
$emitter->attach(new Cookie($value));
} elseif ($value !== false) {
throw new Iae('cookies must be an array, true, or CookieJarInterface');
}
break;
case 'events':
if (!is_array($value)) {
throw new Iae('events must be an array');
}
$this->attachListeners($request,
$this->prepareListeners(
$value,
['before', 'complete', 'error', 'progress', 'end']
)
);
break;
case 'subscribers':
if (!is_array($value)) {
throw new Iae('subscribers must be an array');
}
foreach ($value as $subscribers) {
$emitter->attach($subscribers);
}
break;
case 'json':
$request->setBody(Stream::factory(json_encode($value)));
if (!$request->hasHeader('Content-Type')) {
$request->setHeader('Content-Type', 'application/json');
}
break;
default:
// Check for custom handler functions.
if (isset($this->customOptions[$key])) {
$fn = $this->customOptions[$key];
$fn($request, $value);
continue 2;
}
throw new Iae("No method can handle the {$key} config key");
}
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Url;
/**
* Request and response factory
*/
interface MessageFactoryInterface
{
/**
* Creates a response
*
* @param string $statusCode HTTP status code
* @param array $headers Response headers
* @param mixed $body Response body
* @param array $options Response options
* - protocol_version: HTTP protocol version
* - header_factory: Factory used to create headers
* - And any other options used by a concrete message implementation
*
* @return ResponseInterface
*/
public function createResponse(
$statusCode,
array $headers = [],
$body = null,
array $options = []
);
/**
* Create a new request based on the HTTP method.
*
* This method accepts an associative array of request options. Below is a
* brief description of each parameter. See
* http://docs.guzzlephp.org/en/latest/clients.html#request-options for a much more
* in-depth description of each parameter.
*
* - headers: Associative array of headers to add to the request
* - body: string|resource|array|StreamInterface request body to send
* - json: mixed Uploads JSON encoded data using an application/json Content-Type header.
* - query: Associative array of query string values to add to the request
* - auth: array|string HTTP auth settings (user, pass[, type="basic"])
* - version: The HTTP protocol version to use with the request
* - cookies: true|false|CookieJarInterface To enable or disable cookies
* - allow_redirects: true|false|array Controls HTTP redirects
* - save_to: string|resource|StreamInterface Where the response is saved
* - events: Associative array of event names to callables or arrays
* - subscribers: Array of event subscribers to add to the request
* - exceptions: Specifies whether or not exceptions are thrown for HTTP protocol errors
* - timeout: Timeout of the request in seconds. Use 0 to wait indefinitely
* - connect_timeout: Number of seconds to wait while trying to connect. (0 to wait indefinitely)
* - verify: SSL validation. True/False or the path to a PEM file
* - cert: Path a SSL cert or array of (path, pwd)
* - ssl_key: Path to a private SSL key or array of (path, pwd)
* - proxy: Specify an HTTP proxy or hash of protocols to proxies
* - debug: Set to true or a resource to view handler specific debug info
* - stream: Set to true to stream a response body rather than download it all up front
* - expect: true/false/integer Controls the "Expect: 100-Continue" header
* - config: Associative array of request config collection options
* - decode_content: true/false/string to control decoding content-encoding responses
*
* @param string $method HTTP method (GET, POST, PUT, etc.)
* @param string|Url $url HTTP URL to connect to
* @param array $options Array of options to apply to the request
*
* @return RequestInterface
* @link http://docs.guzzlephp.org/en/latest/clients.html#request-options
*/
public function createRequest($method, $url, array $options = []);
}

View File

@@ -0,0 +1,136 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Stream\StreamInterface;
/**
* Request and response message interface
*/
interface MessageInterface
{
/**
* Get a string representation of the message
*
* @return string
*/
public function __toString();
/**
* Get the HTTP protocol version of the message
*
* @return string
*/
public function getProtocolVersion();
/**
* Sets the body of the message.
*
* The body MUST be a StreamInterface object. Setting the body to null MUST
* remove the existing body.
*
* @param StreamInterface|null $body Body.
*/
public function setBody(StreamInterface $body = null);
/**
* Get the body of the message
*
* @return StreamInterface|null
*/
public function getBody();
/**
* Gets all message headers.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->getHeaders() as $name => $values) {
* echo $name . ": " . implode(", ", $values);
* }
*
* @return array Returns an associative array of the message's headers.
*/
public function getHeaders();
/**
* Retrieve a header by the given case-insensitive name.
*
* @param string $header Case-insensitive header name.
*
* @return string
*/
public function getHeader($header);
/**
* Retrieves a header by the given case-insensitive name as an array of strings.
*
* @param string $header Case-insensitive header name.
*
* @return string[]
*/
public function getHeaderAsArray($header);
/**
* Checks if a header exists by the given case-insensitive name.
*
* @param string $header Case-insensitive header name.
*
* @return bool Returns true if any header names match the given header
* name using a case-insensitive string comparison. Returns false if
* no matching header name is found in the message.
*/
public function hasHeader($header);
/**
* Remove a specific header by case-insensitive name.
*
* @param string $header Case-insensitive header name.
*/
public function removeHeader($header);
/**
* Appends a header value to any existing values associated with the
* given header name.
*
* @param string $header Header name to add
* @param string $value Value of the header
*/
public function addHeader($header, $value);
/**
* Merges in an associative array of headers.
*
* Each array key MUST be a string representing the case-insensitive name
* of a header. Each value MUST be either a string or an array of strings.
* For each value, the value is appended to any existing header of the same
* name, or, if a header does not already exist by the given name, then the
* header is added.
*
* @param array $headers Associative array of headers to add to the message
*/
public function addHeaders(array $headers);
/**
* Sets a header, replacing any existing values of any headers with the
* same case-insensitive name.
*
* The header values MUST be a string or an array of strings.
*
* @param string $header Header name
* @param string|array $value Header value(s)
*/
public function setHeader($header, $value);
/**
* Sets headers, replacing any headers that have already been set on the
* message.
*
* The array keys MUST be a string. The array values must be either a
* string or an array of strings.
*
* @param array $headers Headers to set.
*/
public function setHeaders(array $headers);
}

View File

@@ -0,0 +1,171 @@
<?php
namespace GuzzleHttp\Message;
/**
* Request and response parser used by Guzzle
*/
class MessageParser
{
/**
* Parse an HTTP request message into an associative array of parts.
*
* @param string $message HTTP request to parse
*
* @return array|bool Returns false if the message is invalid
*/
public function parseRequest($message)
{
if (!($parts = $this->parseMessage($message))) {
return false;
}
// Parse the protocol and protocol version
if (isset($parts['start_line'][2])) {
$startParts = explode('/', $parts['start_line'][2]);
$protocol = strtoupper($startParts[0]);
$version = isset($startParts[1]) ? $startParts[1] : '1.1';
} else {
$protocol = 'HTTP';
$version = '1.1';
}
$parsed = [
'method' => strtoupper($parts['start_line'][0]),
'protocol' => $protocol,
'protocol_version' => $version,
'headers' => $parts['headers'],
'body' => $parts['body']
];
$parsed['request_url'] = $this->getUrlPartsFromMessage(
(isset($parts['start_line'][1]) ? $parts['start_line'][1] : ''), $parsed);
return $parsed;
}
/**
* Parse an HTTP response message into an associative array of parts.
*
* @param string $message HTTP response to parse
*
* @return array|bool Returns false if the message is invalid
*/
public function parseResponse($message)
{
if (!($parts = $this->parseMessage($message))) {
return false;
}
list($protocol, $version) = explode('/', trim($parts['start_line'][0]));
return [
'protocol' => $protocol,
'protocol_version' => $version,
'code' => $parts['start_line'][1],
'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '',
'headers' => $parts['headers'],
'body' => $parts['body']
];
}
/**
* Parse a message into parts
*
* @param string $message Message to parse
*
* @return array|bool
*/
private function parseMessage($message)
{
if (!$message) {
return false;
}
$startLine = null;
$headers = [];
$body = '';
// Iterate over each line in the message, accounting for line endings
$lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
$line = $lines[$i];
// If two line breaks were encountered, then this is the end of body
if (empty($line)) {
if ($i < $totalLines - 1) {
$body = implode('', array_slice($lines, $i + 2));
}
break;
}
// Parse message headers
if (!$startLine) {
$startLine = explode(' ', $line, 3);
} elseif (strpos($line, ':')) {
$parts = explode(':', $line, 2);
$key = trim($parts[0]);
$value = isset($parts[1]) ? trim($parts[1]) : '';
if (!isset($headers[$key])) {
$headers[$key] = $value;
} elseif (!is_array($headers[$key])) {
$headers[$key] = [$headers[$key], $value];
} else {
$headers[$key][] = $value;
}
}
}
return [
'start_line' => $startLine,
'headers' => $headers,
'body' => $body
];
}
/**
* Create URL parts from HTTP message parts
*
* @param string $requestUrl Associated URL
* @param array $parts HTTP message parts
*
* @return array
*/
private function getUrlPartsFromMessage($requestUrl, array $parts)
{
// Parse the URL information from the message
$urlParts = ['path' => $requestUrl, 'scheme' => 'http'];
// Check for the Host header
if (isset($parts['headers']['Host'])) {
$urlParts['host'] = $parts['headers']['Host'];
} elseif (isset($parts['headers']['host'])) {
$urlParts['host'] = $parts['headers']['host'];
} else {
$urlParts['host'] = null;
}
if (false === strpos($urlParts['host'], ':')) {
$urlParts['port'] = '';
} else {
$hostParts = explode(':', $urlParts['host']);
$urlParts['host'] = trim($hostParts[0]);
$urlParts['port'] = (int) trim($hostParts[1]);
if ($urlParts['port'] == 443) {
$urlParts['scheme'] = 'https';
}
}
// Check if a query is present
$path = $urlParts['path'];
$qpos = strpos($path, '?');
if ($qpos) {
$urlParts['query'] = substr($path, $qpos + 1);
$urlParts['path'] = substr($path, 0, $qpos);
} else {
$urlParts['query'] = '';
}
return $urlParts;
}
}

View File

@@ -0,0 +1,195 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Collection;
use GuzzleHttp\Event\HasEmitterTrait;
use GuzzleHttp\Subscriber\Prepare;
use GuzzleHttp\Url;
/**
* HTTP request class to send requests
*/
class Request extends AbstractMessage implements RequestInterface
{
use HasEmitterTrait;
/** @var Url HTTP Url */
private $url;
/** @var string HTTP method */
private $method;
/** @var Collection Transfer options */
private $transferOptions;
/**
* @param string $method HTTP method
* @param string|Url $url HTTP URL to connect to. The URI scheme,
* host header, and URI are parsed from the full URL. If query string
* parameters are present they will be parsed as well.
* @param array|Collection $headers HTTP headers
* @param mixed $body Body to send with the request
* @param array $options Array of options to use with the request
* - emitter: Event emitter to use with the request
*/
public function __construct(
$method,
$url,
$headers = [],
$body = null,
array $options = []
) {
$this->setUrl($url);
$this->method = strtoupper($method);
$this->handleOptions($options);
$this->transferOptions = new Collection($options);
$this->addPrepareEvent();
if ($body !== null) {
$this->setBody($body);
}
if ($headers) {
foreach ($headers as $key => $value) {
$this->addHeader($key, $value);
}
}
}
public function __clone()
{
if ($this->emitter) {
$this->emitter = clone $this->emitter;
}
$this->transferOptions = clone $this->transferOptions;
$this->url = clone $this->url;
}
public function setUrl($url)
{
$this->url = $url instanceof Url ? $url : Url::fromString($url);
$this->updateHostHeaderFromUrl();
}
public function getUrl()
{
return (string) $this->url;
}
public function setQuery($query)
{
$this->url->setQuery($query);
}
public function getQuery()
{
return $this->url->getQuery();
}
public function setMethod($method)
{
$this->method = strtoupper($method);
}
public function getMethod()
{
return $this->method;
}
public function getScheme()
{
return $this->url->getScheme();
}
public function setScheme($scheme)
{
$this->url->setScheme($scheme);
}
public function getPort()
{
return $this->url->getPort();
}
public function setPort($port)
{
$this->url->setPort($port);
$this->updateHostHeaderFromUrl();
}
public function getHost()
{
return $this->url->getHost();
}
public function setHost($host)
{
$this->url->setHost($host);
$this->updateHostHeaderFromUrl();
}
public function getPath()
{
return '/' . ltrim($this->url->getPath(), '/');
}
public function setPath($path)
{
$this->url->setPath($path);
}
public function getResource()
{
$resource = $this->getPath();
if ($query = (string) $this->url->getQuery()) {
$resource .= '?' . $query;
}
return $resource;
}
public function getConfig()
{
return $this->transferOptions;
}
protected function handleOptions(array &$options)
{
parent::handleOptions($options);
// Use a custom emitter if one is specified, and remove it from
// options that are exposed through getConfig()
if (isset($options['emitter'])) {
$this->emitter = $options['emitter'];
unset($options['emitter']);
}
}
/**
* Adds a subscriber that ensures a request's body is prepared before
* sending.
*/
private function addPrepareEvent()
{
static $subscriber;
if (!$subscriber) {
$subscriber = new Prepare();
}
$this->getEmitter()->attach($subscriber);
}
private function updateHostHeaderFromUrl()
{
$port = $this->url->getPort();
$scheme = $this->url->getScheme();
if ($host = $this->url->getHost()) {
if (($port == 80 && $scheme == 'http') ||
($port == 443 && $scheme == 'https')
) {
$this->setHeader('Host', $host);
} else {
$this->setHeader('Host', "{$host}:{$port}");
}
}
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Event\HasEmitterInterface;
use GuzzleHttp\Query;
/**
* Generic HTTP request interface
*/
interface RequestInterface extends MessageInterface, HasEmitterInterface
{
/**
* Sets the request URL.
*
* The URL MUST be a string, or an object that implements the
* `__toString()` method.
*
* @param string $url Request URL.
*
* @throws \InvalidArgumentException If the URL is invalid.
*/
public function setUrl($url);
/**
* Gets the request URL as a string.
*
* @return string Returns the URL as a string.
*/
public function getUrl();
/**
* Get the resource part of the the request, including the path, query
* string, and fragment.
*
* @return string
*/
public function getResource();
/**
* Get the collection of key value pairs that will be used as the query
* string in the request.
*
* @return Query
*/
public function getQuery();
/**
* Set the query string used by the request
*
* @param array|Query $query Query to set
*/
public function setQuery($query);
/**
* Get the HTTP method of the request.
*
* @return string
*/
public function getMethod();
/**
* Set the HTTP method of the request.
*
* @param string $method HTTP method
*/
public function setMethod($method);
/**
* Get the URI scheme of the request (http, https, etc.).
*
* @return string
*/
public function getScheme();
/**
* Set the URI scheme of the request (http, https, etc.).
*
* @param string $scheme Scheme to set
*/
public function setScheme($scheme);
/**
* Get the port scheme of the request (e.g., 80, 443, etc.).
*
* @return int
*/
public function getPort();
/**
* Set the port of the request.
*
* Setting a port modifies the Host header of a request as necessary.
*
* @param int $port Port to set
*/
public function setPort($port);
/**
* Get the host of the request.
*
* @return string
*/
public function getHost();
/**
* Set the host of the request including an optional port.
*
* Including a port in the host argument will explicitly change the port of
* the request. If no port is found, the default port of the current
* request scheme will be utilized.
*
* @param string $host Host to set (e.g. www.yahoo.com, www.yahoo.com:80)
*/
public function setHost($host);
/**
* Get the path of the request (e.g. '/', '/index.html').
*
* @return string
*/
public function getPath();
/**
* Set the path of the request (e.g. '/', '/index.html').
*
* @param string|array $path Path to set or array of segments to implode
*/
public function setPath($path);
/**
* Get the request's configuration options.
*
* @return \GuzzleHttp\Collection
*/
public function getConfig();
}

View File

@@ -0,0 +1,208 @@
<?php
namespace GuzzleHttp\Message;
use GuzzleHttp\Exception\ParseException;
use GuzzleHttp\Exception\XmlParseException;
use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Utils;
/**
* Guzzle HTTP response object
*/
class Response extends AbstractMessage implements ResponseInterface
{
/** @var array Mapping of status codes to reason phrases */
private static $statusTexts = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Reserved for WebDAV advanced collections expired proposal',
426 => 'Upgrade required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates (Experimental)',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
510 => 'Not Extended',
511 => 'Network Authentication Required',
];
/** @var string The reason phrase of the response (human readable code) */
private $reasonPhrase;
/** @var string The status code of the response */
private $statusCode;
/** @var string The effective URL that returned this response */
private $effectiveUrl;
/**
* @param int|string $statusCode The response status code (e.g. 200)
* @param array $headers The response headers
* @param StreamInterface $body The body of the response
* @param array $options Response message options
* - reason_phrase: Set a custom reason phrase
* - protocol_version: Set a custom protocol version
*/
public function __construct(
$statusCode,
array $headers = [],
StreamInterface $body = null,
array $options = []
) {
$this->statusCode = (int) $statusCode;
$this->handleOptions($options);
// Assume a reason phrase if one was not applied as an option
if (!$this->reasonPhrase &&
isset(self::$statusTexts[$this->statusCode])
) {
$this->reasonPhrase = self::$statusTexts[$this->statusCode];
}
if ($headers) {
$this->setHeaders($headers);
}
if ($body) {
$this->setBody($body);
}
}
public function getStatusCode()
{
return $this->statusCode;
}
public function setStatusCode($code)
{
return $this->statusCode = (int) $code;
}
public function getReasonPhrase()
{
return $this->reasonPhrase;
}
public function setReasonPhrase($phrase)
{
return $this->reasonPhrase = $phrase;
}
public function json(array $config = [])
{
try {
return Utils::jsonDecode(
(string) $this->getBody(),
isset($config['object']) ? !$config['object'] : true,
512,
isset($config['big_int_strings']) ? JSON_BIGINT_AS_STRING : 0
);
} catch (\InvalidArgumentException $e) {
throw new ParseException(
$e->getMessage(),
$this
);
}
}
public function xml(array $config = [])
{
$disableEntities = libxml_disable_entity_loader(true);
$internalErrors = libxml_use_internal_errors(true);
try {
// Allow XML to be retrieved even if there is no response body
$xml = new \SimpleXMLElement(
(string) $this->getBody() ?: '<root />',
isset($config['libxml_options']) ? $config['libxml_options'] : LIBXML_NONET,
false,
isset($config['ns']) ? $config['ns'] : '',
isset($config['ns_is_prefix']) ? $config['ns_is_prefix'] : false
);
libxml_disable_entity_loader($disableEntities);
libxml_use_internal_errors($internalErrors);
} catch (\Exception $e) {
libxml_disable_entity_loader($disableEntities);
libxml_use_internal_errors($internalErrors);
throw new XmlParseException(
'Unable to parse response body into XML: ' . $e->getMessage(),
$this,
$e,
(libxml_get_last_error()) ?: null
);
}
return $xml;
}
public function getEffectiveUrl()
{
return $this->effectiveUrl;
}
public function setEffectiveUrl($url)
{
$this->effectiveUrl = $url;
}
/**
* Accepts and modifies the options provided to the response in the
* constructor.
*
* @param array $options Options array passed by reference.
*/
protected function handleOptions(array &$options = [])
{
parent::handleOptions($options);
if (isset($options['reason_phrase'])) {
$this->reasonPhrase = $options['reason_phrase'];
}
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace GuzzleHttp\Message;
/**
* Represents an HTTP response message.
*/
interface ResponseInterface extends MessageInterface
{
/**
* Gets the response Status-Code.
*
* The Status-Code is a 3-digit integer result code of the server's attempt
* to understand and satisfy the request.
*
* @return int Status code.
*/
public function getStatusCode();
/**
* Sets the status code of this response.
*
* @param int $code The 3-digit integer result code to set.
*/
public function setStatusCode($code);
/**
* Gets the response Reason-Phrase, a short textual description of the
* Status-Code.
*
* Because a Reason-Phrase is not a required element in response
* Status-Line, the Reason-Phrase value MAY be null. Implementations MAY
* choose to return the default RFC 2616 recommended reason phrase for the
* response's Status-Code.
*
* @return string|null Reason phrase, or null if unknown.
*/
public function getReasonPhrase();
/**
* Sets the Reason-Phrase of the response.
*
* If no Reason-Phrase is specified, implementations MAY choose to default
* to the RFC 2616 recommended reason phrase for the response's Status-Code.
*
* @param string $phrase The Reason-Phrase to set.
*/
public function setReasonPhrase($phrase);
/**
* Get the effective URL that resulted in this response (e.g. the last
* redirect URL).
*
* @return string
*/
public function getEffectiveUrl();
/**
* Set the effective URL that resulted in this response (e.g. the last
* redirect URL).
*
* @param string $url Effective URL
*/
public function setEffectiveUrl($url);
/**
* Parse the JSON response body and return the JSON decoded data.
*
* @param array $config Associative array of configuration settings used
* to control how the JSON data is parsed. Concrete implementations MAY
* add further configuration settings as needed, but they MUST implement
* functionality for the following options:
*
* - object: Set to true to parse JSON objects as PHP objects rather
* than associative arrays. Defaults to false.
* - big_int_strings: When set to true, large integers are converted to
* strings rather than floats. Defaults to false.
*
* Implementations are free to add further configuration settings as
* needed.
*
* @return mixed Returns the JSON decoded data based on the provided
* parse settings.
* @throws \RuntimeException if the response body is not in JSON format
*/
public function json(array $config = []);
/**
* Parse the XML response body and return a \SimpleXMLElement.
*
* In order to prevent XXE attacks, this method disables loading external
* entities. If you rely on external entities, then you must parse the
* XML response manually by accessing the response body directly.
*
* @param array $config Associative array of configuration settings used
* to control how the XML is parsed. Concrete implementations MAY add
* further configuration settings as needed, but they MUST implement
* functionality for the following options:
*
* - ns: Set to a string to represent the namespace prefix or URI
* - ns_is_prefix: Set to true to specify that the NS is a prefix rather
* than a URI (defaults to false).
* - libxml_options: Bitwise OR of the libxml option constants
* listed at http://php.net/manual/en/libxml.constants.php
* (defaults to LIBXML_NONET)
*
* @return \SimpleXMLElement
* @throws \RuntimeException if the response body is not in XML format
* @link http://websec.io/2012/08/27/Preventing-XXE-in-PHP.html
*/
public function xml(array $config = []);
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;