v1.2.7.415

This commit is contained in:
RainLoop Team 2013-09-29 02:02:47 +04:00
parent 5c1e7bc676
commit 54e2645fcf
436 changed files with 407 additions and 347 deletions

View file

@ -0,0 +1,154 @@
<?php
namespace Buzz\Message;
abstract class AbstractMessage implements MessageInterface
{
private $headers = array();
private $content;
/**
* Returns the value of a header.
*
* @param string $name
* @param string|boolean $glue Glue for implode, or false to return an array
*
* @return string|array|null
*/
public function getHeader($name, $glue = "\r\n")
{
$needle = $name.':';
$values = array();
foreach ($this->getHeaders() as $header) {
if (0 === stripos($header, $needle)) {
$values[] = trim(substr($header, strlen($needle)));
}
}
if (false === $glue) {
return $values;
} else {
return count($values) ? implode($glue, $values) : null;
}
}
/**
* Returns a header's attributes.
*
* @param string $name A header name
*
* @return array An associative array of attributes
*/
public function getHeaderAttributes($name)
{
$attributes = array();
foreach ($this->getHeader($name, false) as $header) {
if (false !== strpos($header, ';')) {
// remove header value
list(, $header) = explode(';', $header, 2);
// loop through attribute key=value pairs
foreach (array_map('trim', explode(';', trim($header))) as $pair) {
list($key, $value) = explode('=', $pair);
$attributes[$key] = $value;
}
}
}
return $attributes;
}
/**
* Returns the value of a particular header attribute.
*
* @param string $header A header name
* @param string $attribute An attribute name
*
* @return string|null The value of the attribute or null if it isn't set
*/
public function getHeaderAttribute($header, $attribute)
{
$attributes = $this->getHeaderAttributes($header);
if (isset($attributes[$attribute])) {
return $attributes[$attribute];
}
}
/**
* Returns the current message as a DOMDocument.
*
* @return \DOMDocument
*/
public function toDomDocument()
{
$revert = libxml_use_internal_errors(true);
$document = new \DOMDocument('1.0', $this->getHeaderAttribute('Content-Type', 'charset') ?: 'UTF-8');
if (0 === strpos($this->getHeader('Content-Type'), 'text/xml')) {
$document->loadXML($this->getContent());
} else {
$document->loadHTML($this->getContent());
}
libxml_use_internal_errors($revert);
return $document;
}
public function setHeaders(array $headers)
{
$this->headers = $this->flattenHeaders($headers);
}
public function addHeader($header)
{
$this->headers[] = $header;
}
public function addHeaders(array $headers)
{
$this->headers = array_merge($this->headers, $this->flattenHeaders($headers));
}
public function getHeaders()
{
return $this->headers;
}
public function setContent($content)
{
$this->content = $content;
}
public function getContent()
{
return $this->content;
}
public function __toString()
{
$string = implode("\r\n", $this->getHeaders())."\r\n";
if ($content = $this->getContent()) {
$string .= "\r\n$content\r\n";
}
return $string;
}
protected function flattenHeaders(array $headers)
{
$flattened = array();
foreach ($headers as $key => $header) {
if (is_int($key)) {
$flattened[] = $header;
} else {
$flattened[] = $key.': '.$header;
}
}
return $flattened;
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Buzz\Message\Factory;
use Buzz\Message\Form\FormRequest;
use Buzz\Message\Request;
use Buzz\Message\RequestInterface;
use Buzz\Message\Response;
class Factory implements FactoryInterface
{
public function createRequest($method = RequestInterface::METHOD_GET, $resource = '/', $host = null)
{
return new Request($method, $resource, $host);
}
public function createFormRequest($method = RequestInterface::METHOD_POST, $resource = '/', $host = null)
{
return new FormRequest($method, $resource, $host);
}
public function createResponse()
{
return new Response();
}
}

View file

@ -0,0 +1,12 @@
<?php
namespace Buzz\Message\Factory;
use Buzz\Message\RequestInterface;
interface FactoryInterface
{
public function createRequest($method = RequestInterface::METHOD_GET, $resource = '/', $host = null);
public function createFormRequest($method = RequestInterface::METHOD_POST, $resource = '/', $host = null);
public function createResponse();
}

View file

@ -0,0 +1,187 @@
<?php
namespace Buzz\Message\Form;
use Buzz\Message\Request;
use Buzz\Exception\LogicException;
/**
* FormRequest.
*
* $request = new FormRequest();
* $request->setField('user[name]', 'Kris Wallsmith');
* $request->setField('user[image]', new FormUpload('/path/to/image.jpg'));
*
* @author Marc Weistroff <marc.weistroff@sensio.com>
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class FormRequest extends Request implements FormRequestInterface
{
private $fields = array();
private $boundary;
/**
* Constructor.
*
* Defaults to POST rather than GET.
*/
public function __construct($method = self::METHOD_POST, $resource = '/', $host = null)
{
parent::__construct($method, $resource, $host);
}
/**
* Sets the value of a form field.
*
* If the value is an array it will be flattened and one field value will
* be added for each leaf.
*/
public function setField($name, $value)
{
if (is_array($value)) {
$this->addFields(array($name => $value));
return;
}
if ('[]' == substr($name, -2)) {
$this->fields[substr($name, 0, -2)][] = $value;
} else {
$this->fields[$name] = $value;
}
}
public function addFields(array $fields)
{
foreach ($this->flattenArray($fields) as $name => $value) {
$this->setField($name, $value);
}
}
public function setFields(array $fields)
{
$this->fields = array();
$this->addFields($fields);
}
public function getFields()
{
return $this->fields;
}
public function getResource()
{
$resource = parent::getResource();
if (!$this->isSafe() || !$this->fields) {
return $resource;
}
// append the query string
$resource .= false === strpos($resource, '?') ? '?' : '&';
$resource .= http_build_query($this->fields);
return $resource;
}
public function setContent($content)
{
throw new \BadMethodCallException('It is not permitted to set the content.');
}
public function getHeaders()
{
$headers = parent::getHeaders();
if ($this->isSafe()) {
return $headers;
}
if ($this->isMultipart()) {
$headers[] = 'Content-Type: multipart/form-data; boundary='.$this->getBoundary();
} else {
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
}
return $headers;
}
public function getContent()
{
if ($this->isSafe()) {
return;
}
if (!$this->isMultipart()) {
return http_build_query($this->fields);
}
$content = '';
foreach ($this->fields as $name => $values) {
$content .= '--'.$this->getBoundary()."\r\n";
if ($values instanceof FormUploadInterface) {
if (!$values->getFilename()) {
throw new LogicException(sprintf('Form upload at "%s" does not include a filename.', $name));
}
$values->setName($name);
$content .= (string) $values;
} else {
foreach (is_array($values) ? $values : array($values) as $value) {
$content .= "Content-Disposition: form-data; name=\"$name\"\r\n";
$content .= "\r\n";
$content .= $value."\r\n";
}
}
}
$content .= '--'.$this->getBoundary().'--';
return $content;
}
// private
private function flattenArray(array $values, $prefix = '', $format = '%s')
{
$flat = array();
foreach ($values as $name => $value) {
$flatName = $prefix.sprintf($format, $name);
if (is_array($value)) {
$flat += $this->flattenArray($value, $flatName, '[%s]');
} else {
$flat[$flatName] = $value;
}
}
return $flat;
}
private function isSafe()
{
return in_array($this->getMethod(), array(self::METHOD_GET, self::METHOD_HEAD));
}
private function isMultipart()
{
foreach ($this->fields as $name => $value) {
if (is_object($value) && $value instanceof FormUploadInterface) {
return true;
}
}
return false;
}
private function getBoundary()
{
if (!$this->boundary) {
$this->boundary = sha1(rand(11111, 99999).time().uniqid());
}
return $this->boundary;
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Buzz\Message\Form;
use Buzz\Message\RequestInterface;
/**
* An HTTP request message sent by a web form.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
interface FormRequestInterface extends RequestInterface
{
/**
* Returns an array of field names and values.
*
* @return array A array of names and values
*/
public function getFields();
/**
* Sets the form fields for the current request.
*
* @param array $fields An array of field names and values
*/
public function setFields(array $fields);
}

View file

@ -0,0 +1,118 @@
<?php
namespace Buzz\Message\Form;
use Buzz\Message\AbstractMessage;
class FormUpload extends AbstractMessage implements FormUploadInterface
{
private $name;
private $filename;
private $contentType;
private $file;
public function __construct($file = null, $contentType = null)
{
if ($file) {
$this->loadContent($file);
}
$this->contentType = $contentType;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getFilename()
{
if ($this->filename) {
return $this->filename;
} elseif ($this->file) {
return basename($this->file);
}
}
public function setFilename($filename)
{
$this->filename = $filename;
}
public function getContentType()
{
return $this->contentType ?: $this->detectContentType() ?: 'application/octet-stream';
}
public function setContentType($contentType)
{
$this->contentType = $contentType;
}
/**
* Prepends Content-Disposition and Content-Type headers.
*/
public function getHeaders()
{
$headers = array('Content-Disposition: form-data');
if ($name = $this->getName()) {
$headers[0] .= sprintf('; name="%s"', $name);
}
if ($filename = $this->getFilename()) {
$headers[0] .= sprintf('; filename="%s"', $filename);
}
if ($contentType = $this->getContentType()) {
$headers[] = 'Content-Type: '.$contentType;
}
return array_merge($headers, parent::getHeaders());
}
/**
* Loads the content from a file.
*/
public function loadContent($file)
{
$this->file = $file;
parent::setContent(null);
}
public function setContent($content)
{
parent::setContent($content);
$this->file = null;
}
public function getFile()
{
return $this->file;
}
public function getContent()
{
return $this->file ? file_get_contents($this->file) : parent::getContent();
}
// private
private function detectContentType()
{
if (!class_exists('finfo', false)) {
return false;
}
$finfo = new \finfo(FILEINFO_MIME_TYPE);
return $this->file ? $finfo->file($this->file) : $finfo->buffer(parent::getContent());
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Buzz\Message\Form;
use Buzz\Message\MessageInterface;
interface FormUploadInterface extends MessageInterface
{
public function setName($name);
public function getFile();
public function getFilename();
public function getContentType();
}

View file

@ -0,0 +1,74 @@
<?php
namespace Buzz\Message;
/**
* An HTTP message.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
interface MessageInterface
{
/**
* Returns a header value.
*
* @param string $name A header name
* @param string|boolean $glue Glue for implode, or false to return an array
*
* @return string|array|null The header value(s)
*/
public function getHeader($name, $glue = "\r\n");
/**
* Returns an array of header lines.
*
* @return array An array of header lines (integer indexes, e.g. ["Header: value"])
*/
public function getHeaders();
/**
* Sets all headers on the current message.
*
* Headers can be complete ["Header: value"] pairs or an associative array ["Header" => "value"]
*
* @param array $headers An array of header lines
*/
public function setHeaders(array $headers);
/**
* Adds a header to this message.
*
* @param string $header A header line
*/
public function addHeader($header);
/**
* Adds a set of headers to this message.
*
* Headers can be complete ["Header: value"] pairs or an associative array ["Header" => "value"]
*
* @param array $headers Headers
*/
public function addHeaders(array $headers);
/**
* Returns the content of the message.
*
* @return string The message content
*/
public function getContent();
/**
* Sets the content of the message.
*
* @param string $content The message content
*/
public function setContent($content);
/**
* Returns the message document.
*
* @return string The message
*/
public function __toString();
}

View file

@ -0,0 +1,174 @@
<?php
namespace Buzz\Message;
use Buzz\Util\Url;
class Request extends AbstractMessage implements RequestInterface
{
private $method;
private $resource;
private $host;
private $protocolVersion = 1.0;
/**
* Constructor.
*
* @param string $method
* @param string $resource
* @param string $host
*/
public function __construct($method = self::METHOD_GET, $resource = '/', $host = null)
{
$this->method = strtoupper($method);
$this->resource = $resource;
$this->host = $host;
}
public function setHeaders(array $headers)
{
parent::setHeaders(array());
foreach ($this->flattenHeaders($headers) as $header) {
$this->addHeader($header);
}
}
public function addHeader($header)
{
if (0 === stripos(substr($header, -8), 'HTTP/1.') && 3 == count($parts = explode(' ', $header))) {
list($method, $resource, $protocolVersion) = $parts;
$this->setMethod($method);
$this->setResource($resource);
$this->setProtocolVersion((float) substr($protocolVersion, 5));
} else {
parent::addHeader($header);
}
}
public function setMethod($method)
{
$this->method = strtoupper($method);
}
public function getMethod()
{
return $this->method;
}
public function setResource($resource)
{
$this->resource = $resource;
}
public function getResource()
{
return $this->resource;
}
public function setHost($host)
{
$this->host = $host;
}
public function getHost()
{
return $this->host;
}
public function setProtocolVersion($protocolVersion)
{
$this->protocolVersion = $protocolVersion;
}
public function getProtocolVersion()
{
return $this->protocolVersion;
}
/**
* A convenience method for getting the full URL of the current request.
*
* @return string
*/
public function getUrl()
{
return $this->getHost().$this->getResource();
}
/**
* A convenience method for populating the current request from a URL.
*
* @param Url|string $url An URL
*/
public function fromUrl($url)
{
if (!$url instanceof Url) {
$url = new Url($url);
}
$url->applyToRequest($this);
}
/**
* Returns true if the current request is secure.
*
* @return boolean
*/
public function isSecure()
{
return 'https' == parse_url($this->getHost(), PHP_URL_SCHEME);
}
/**
* Merges cookie headers on the way out.
*/
public function getHeaders()
{
return $this->mergeCookieHeaders(parent::getHeaders());
}
/**
* Returns a string representation of the current request.
*
* @return string
*/
public function __toString()
{
$string = sprintf("%s %s HTTP/%.1f\r\n", $this->getMethod(), $this->getResource(), $this->getProtocolVersion());
if ($host = $this->getHost()) {
$string .= 'Host: '.$host."\r\n";
}
if ($parent = trim(parent::__toString())) {
$string .= $parent."\r\n";
}
return $string;
}
// private
private function mergeCookieHeaders(array $headers)
{
$cookieHeader = null;
$needle = 'Cookie:';
foreach ($headers as $i => $header) {
if (0 !== stripos($header, $needle)) {
continue;
}
if (null === $cookieHeader) {
$cookieHeader = $i;
} else {
$headers[$cookieHeader] .= '; '.trim(substr($header, strlen($needle)));
unset($headers[$i]);
}
}
return array_values($headers);
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Buzz\Message;
/**
* An HTTP request message.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
interface RequestInterface extends MessageInterface
{
const METHOD_OPTIONS = 'OPTIONS';
const METHOD_GET = 'GET';
const METHOD_HEAD = 'HEAD';
const METHOD_POST = 'POST';
const METHOD_PUT = 'PUT';
const METHOD_DELETE = 'DELETE';
const METHOD_PATCH = 'PATCH';
/**
* Returns the HTTP method of the current request.
*
* @return string An HTTP method
*/
public function getMethod();
/**
* Sets the HTTP method of the current request.
*
* @param string $method The request method
*/
public function setMethod($method);
/**
* Returns the resource portion of the request line.
*
* @return string The resource requested
*/
public function getResource();
/**
* Sets the resource for the current request.
*
* @param string $resource The resource being requested
*/
public function setResource($resource);
/**
* Returns the protocol version of the current request.
*
* @return float The protocol version
*/
public function getProtocolVersion();
/**
* Returns the value of the host header.
*
* @return string|null The host
*/
public function getHost();
/**
* Sets the host for the current request.
*
* @param string $host The host
*/
public function setHost($host);
/**
* Checks if the current request is secure.
*
* @return Boolean True if the request is secure
*/
public function isSecure();
}

View file

@ -0,0 +1,193 @@
<?php
namespace Buzz\Message;
class Response extends AbstractMessage
{
private $protocolVersion;
private $statusCode;
private $reasonPhrase;
/**
* Returns the protocol version of the current response.
*
* @return float
*/
public function getProtocolVersion()
{
if (null === $this->protocolVersion) {
$this->parseStatusLine();
}
return $this->protocolVersion ?: null;
}
/**
* Returns the status code of the current response.
*
* @return integer
*/
public function getStatusCode()
{
if (null === $this->statusCode) {
$this->parseStatusLine();
}
return $this->statusCode ?: null;
}
/**
* Returns the reason phrase for the current response.
*
* @return string
*/
public function getReasonPhrase()
{
if (null === $this->reasonPhrase) {
$this->parseStatusLine();
}
return $this->reasonPhrase ?: null;
}
public function setHeaders(array $headers)
{
parent::setHeaders($headers);
$this->resetStatusLine();
}
public function addHeader($header)
{
parent::addHeader($header);
$this->resetStatusLine();
}
public function addHeaders(array $headers)
{
parent::addHeaders($headers);
$this->resetStatusLine();
}
/**
* Is response invalid?
*
* @return Boolean
*/
public function isInvalid()
{
return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
}
/**
* Is response informative?
*
* @return Boolean
*/
public function isInformational()
{
return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
}
/**
* Is response successful?
*
* @return Boolean
*/
public function isSuccessful()
{
return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
}
/**
* Is the response a redirect?
*
* @return Boolean
*/
public function isRedirection()
{
return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
}
/**
* Is there a client error?
*
* @return Boolean
*/
public function isClientError()
{
return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
}
/**
* Was there a server side error?
*
* @return Boolean
*/
public function isServerError()
{
return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
}
/**
* Is the response OK?
*
* @return Boolean
*/
public function isOk()
{
return 200 === $this->getStatusCode();
}
/**
* Is the reponse forbidden?
*
* @return Boolean
*/
public function isForbidden()
{
return 403 === $this->getStatusCode();
}
/**
* Is the response a not found error?
*
* @return Boolean
*/
public function isNotFound()
{
return 404 === $this->getStatusCode();
}
/**
* Is the response empty?
*
* @return Boolean
*/
public function isEmpty()
{
return in_array($this->getStatusCode(), array(201, 204, 304));
}
// private
private function parseStatusLine()
{
$headers = $this->getHeaders();
if (isset($headers[0]) && 3 == count($parts = explode(' ', $headers[0], 3))) {
$this->protocolVersion = (float) $parts[0];
$this->statusCode = (integer) $parts[1];
$this->reasonPhrase = $parts[2];
} else {
$this->protocolVersion = $this->statusCode = $this->reasonPhrase = false;
}
}
private function resetStatusLine()
{
$this->protocolVersion = $this->statusCode = $this->reasonPhrase = null;
}
}