Skip to content
Browse files

First pass at disambiguating request/response content type. WARNING: …

…BC break - going forward, usage of the `$type` instance property should be replaced with the `type()` method.
  • Loading branch information...
1 parent 9432658 commit 854171dcfd57be515e87f2c346a3ac124b59630d @nateabele nateabele committed with gwoo
View
2 net/Message.php
@@ -92,7 +92,7 @@ public function __construct(array $config = array()) {
);
$config += $defaults;
- foreach (array_filter($config) as $key => $value) {
+ foreach (array_intersect_key(array_filter($config), $defaults) as $key => $value) {
$this->{$key} = $value;
}
parent::__construct($config);
View
2 net/http/Media.php
@@ -551,7 +551,7 @@ public static function path($path, $type, array $options = array()) {
*
* @param object $response A Response object into which the operation will be
* rendered. The content of the render operation will be assigned to the `$body`
- * property of the object, the `'Content-type'` header will be set accordingly, and it
+ * property of the object, the `'Content-Type'` header will be set accordingly, and it
* will be returned.
* @param mixed $data The data (usually an associative array) to be rendered in the response.
* @param array $options Any options specific to the response being rendered, such as type
View
33 net/http/Message.php
@@ -40,7 +40,7 @@ class Message extends \lithium\net\Message {
*
* @var string
*/
- protected $_type = 'html';
+ protected $_type = null;
/**
* Classes used by `Request`.
@@ -85,6 +85,7 @@ public function __construct(array $config = array()) {
if (strpos($this->host, '/') !== false) {
list($this->host, $this->path) = explode('/', $this->host, 2);
}
+ if ($this->headers);
$this->path = str_replace('//', '/', "/{$this->path}");
$this->protocol = $this->protocol ?: "HTTP/{$this->version}";
}
@@ -131,25 +132,39 @@ public function headers($key = null, $value = null) {
}
/**
- * Sets/Gets the content type
+ * Sets/gets the content type.
*
- * @param string $type a full content type i.e. `'application/json'` or simple name `'json'`
+ * @param string $type A full content type i.e. `'application/json'` or simple name `'json'`
* @return string A simple content type name, i.e. `'html'`, `'xml'`, `'json'`, etc., depending
* on the content type of the request.
*/
public function type($type = null) {
- if ($type == null && $type !== false) {
+ if ($type === false) {
+ unset($this->headers['Content-Type']);
+ $this->_type = null;
+ return;
+ }
+ $media = $this->_classes['media'];
+
+ if (!$type && $this->_type) {
return $this->_type;
}
- if (strpos($type, '/')) {
- $media = $this->_classes['media'];
+ $headers = $this->headers + array('Content-Type' => null);
+ $type = $type ?: $headers['Content-Type'];
+
+ if (!$type) {
+ return;
+ }
+ $header = $type;
+ if (!strpos($type, '/')) {
if (!$data = $media::type($type)) {
- return $this->_type;
+ return false;
}
- $type = is_array($data) ? reset($data) : $data;
+ $header = is_array($data['content']) ? reset($data['content']) : $data['content'];
}
- return $this->_type = $type;
+ $this->headers['Content-Type'] = $header;
+ return ($this->_type = $type);
}
}
View
3 net/http/Request.php
@@ -233,7 +233,8 @@ public function to($format, array $options = array()) {
if (in_array($options['method'], array('POST', 'PUT', 'PATCH'))) {
$media = $this->_classes['media'];
if ($type = $media::type($this->_type)) {
- $this->headers('Content-Type', $type['content'][0]);
+ $type = is_array($type['content']) ? reset($type['content']) : $type['content'];
+ $this->headers('Content-Type', $type);
}
}
View
44 net/http/Response.php
@@ -21,13 +21,6 @@ class Response extends \lithium\net\http\Message {
public $status = array('code' => 200, 'message' => 'OK');
/**
- * Content Type.
- *
- * @var string
- */
- public $type = 'text/html';
-
- /**
* Character encoding.
*
* @var string
@@ -95,7 +88,7 @@ class Response extends \lithium\net\http\Message {
* @param array $config
*/
public function __construct(array $config = array()) {
- $defaults = array('message' => null);
+ $defaults = array('message' => null, 'type' => null);
$config += $defaults;
parent::__construct($config);
}
@@ -114,17 +107,21 @@ protected function _init() {
if (isset($this->headers['Transfer-Encoding'])) {
$this->body = $this->_httpChunkedDecode($this->body);
}
- if (isset($this->headers['Content-Type'])) {
- $pattern = '/([-\w\/\.+]+)(;\s*?charset=(.+))?/i';
- preg_match($pattern, $this->headers['Content-Type'], $match);
+ if ($type = $this->_config['type']) {
+ $this->type($type);
+ }
+ if (!isset($this->headers['Content-Type'])) {
+ return;
+ }
+ $pattern = '/([-\w\/\.+]+)(;\s*?charset=(.+))?/i';
+ preg_match($pattern, $this->headers['Content-Type'], $match);
- if (isset($match[1])) {
- $this->type = trim($match[1]);
- $this->body = $this->_decode($this->body);
- }
- if (isset($match[3])) {
- $this->encoding = strtoupper(trim($match[3]));
- }
+ if (isset($match[1])) {
+ $this->type(trim($match[1]));
+ $this->body = $this->_decode($this->body);
+ }
+ if (isset($match[3])) {
+ $this->encoding = strtoupper(trim($match[3]));
}
}
@@ -136,10 +133,11 @@ protected function _init() {
*/
protected function _decode($body) {
$media = $this->_classes['media'];
- if ($type = $media::type($this->_type)) {
- $body = $media::decode($this->_type, $body) ?: $body;
+
+ if (!$type = $media::type($this->_type)) {
+ return $body;
}
- return $body;
+ return $media::decode($this->_type, $body) ?: $body;
}
/**
@@ -248,8 +246,8 @@ protected function _httpChunkedDecode($body) {
* @return string
*/
public function __toString() {
- if ($this->type != 'text/html' && !isset($this->headers['Content-Type'])) {
- $this->headers['Content-Type'] = $this->type;
+ if ($this->_type != 'text/html' && !isset($this->headers['Content-Type'])) {
+ $this->headers['Content-Type'] = $this->type();
}
$first = "{$this->protocol} {$this->status['code']} {$this->status['message']}";
$response = array($first, join("\r\n", $this->headers()), "", $this->body());
View
6 tests/cases/net/http/MediaTest.php
@@ -431,7 +431,7 @@ public function testUnregisteredContentHandler() {
public function testManualContentHandling() {
Media::type('custom', 'text/x-custom');
$response = new Response();
- $response->type = 'custom';
+ $response->type('custom');
Media::render($response, 'Hello, world!', array(
'layout' => false,
@@ -463,7 +463,7 @@ public function testRequestOptionMerging() {
$request->params['foo'] = 'bar';
$response = new Response();
- $response->type = 'custom';
+ $response->type('custom');
Media::render($response, null, compact('request') + array(
'layout' => false,
@@ -494,7 +494,7 @@ public function testRenderWithOptionsMerging() {
$request->params['controller'] = 'pages';
$response = new Response();
- $response->type = 'html';
+ $response->type('html');
$this->expectException('/Template not found/');
Media::render($response, null, compact('request'));
View
15 tests/cases/net/http/RequestTest.php
@@ -326,6 +326,21 @@ public function testQueryParamsConstructed() {
$result = $request->queryString(array('param3' => 3));
$this->assertEqual($expected, $result);
}
+
+ public function testKeepDefinedContentTypeHeaderOnPost() {
+ $request = new Request(array(
+ 'method' => 'POST',
+ 'headers' => array('Content-Type' => 'text/x-test')
+ ));
+ $expected = 'Content-Type: text/x-test';
+ $result = $request->headers();
+ $message = "Expected value `{$expected}` not found in result.";
+ $this->assertTrue(in_array($expected, $result), $message);
+
+ $expected = '#Content-Type: text/x-test#';
+ $result = $request->to('string');
+ $this->assertPattern($expected, $result);
+ }
}
?>
View
119 tests/cases/net/http/ResponseTest.php
@@ -51,33 +51,33 @@ public function testParsingContentTypeWithEncoding() {
$response = new Response(array('headers' => array(
'Content-Type' => 'text/xml;charset=UTF-8'
)));
- $this->assertEqual('text/xml', $response->type);
+ $this->assertEqual('text/xml', $response->type());
$this->assertEqual('UTF-8', $response->encoding);
$response = new Response(array('headers' => array(
'Content-Type' => 'application/soap+xml; charset=iso-8859-1'
)));
- $this->assertEqual('application/soap+xml', $response->type);
+ $this->assertEqual('application/soap+xml', $response->type());
$this->assertEqual('ISO-8859-1', $response->encoding);
// Content type WITHOUT space between type and charset
$response = new Response(array('headers' => array(
'Content-Type' => 'application/json;charset=iso-8859-1'
)));
- $this->assertEqual('application/json', $response->type);
+ $this->assertEqual('json', $response->type());
$this->assertEqual('ISO-8859-1', $response->encoding);
// Content type WITH ONE space between type and charset
$response = new Response(array('headers' => array(
'Content-Type' => 'application/json; charset=iso-8859-1'
)));
- $this->assertEqual('application/json', $response->type);
+ $this->assertEqual('json', $response->type());
$this->assertEqual('ISO-8859-1', $response->encoding);
$response = new Response(array('headers' => array(
'Content-Type' => 'application/json; charset=iso-8859-1'
)));
- $this->assertEqual('application/json', $response->type);
+ $this->assertEqual('json', $response->type());
$this->assertEqual('ISO-8859-1', $response->encoding);
}
@@ -85,7 +85,7 @@ public function testParsingContentTypeWithoutEncoding() {
$response = new Response(array('headers' => array(
'Content-Type' => 'application/json'
)));
- $this->assertEqual('application/json', $response->type);
+ $this->assertEqual('json', $response->type());
$this->assertEqual('UTF-8', $response->encoding); //default
}
@@ -93,7 +93,7 @@ public function testParsingContentTypeWithVersionNumber() {
$response = new Response(array('headers' => array(
'Content-Type' => 'application/x-amz-json-1.0'
)));
- $this->assertEqual('application/x-amz-json-1.0', $response->type);
+ $this->assertEqual('application/x-amz-json-1.0', $response->type());
}
public function testConstructionWithBody() {
@@ -116,7 +116,7 @@ public function testParseMessage() {
$response = new Response(compact('message'));
$this->assertEqual($message, (string) $response);
- $this->assertEqual('application/json', $response->type);
+ $this->assertEqual('application/json', $response->type());
$this->assertEqual('ISO-8859-1', $response->encoding);
$this->assertEqual('404', $response->status['code']);
$this->assertEqual('Not Found', $response->status['message']);
@@ -128,6 +128,46 @@ public function testParseMessage() {
$this->assertEqual($expected, (string) $response);
}
+ public function testParseMessageWithContentTypeHeaderSetsType() {
+ $response = new Response(array(
+ 'message' => "Content-type: text/x-test-a\r\n\r\nfoo"
+ ));
+ $this->assertEqual('text/x-test-a', $response->type());
+ }
+
+ public function testContentTypeHeaderAndTypePropertyAreSynchronized() {
+ $response = new Response(array(
+ 'message' => "Content-type: text/x-test-a\r\n\r\nfoo"
+ ));
+ $this->assertEqual($response->type(), $response->headers('Content-Type'));
+
+ $response = new Response(array(
+ 'headers' => array('Content-Type' => 'text/x-test-a')
+ ));
+ $this->assertEqual($response->type(), $response->headers('Content-Type'));
+
+ $response = new Response(array(
+ 'type' => 'text/x-test-a'
+ ));
+ $this->assertEqual($response->type(), $response->headers('Content-Type'));
+ }
+
+ public function testParseMessageHeadersMerging() {
+ $response = new Response(array(
+ 'message' => "Content-type: text/x-test-a\r\nX-Test-A: foo\r\n\r\nfoo",
+ 'headers' => array(
+ 'Content-Type' => 'text/x-test-b',
+ 'X-Test-B' => 'bar'
+ )
+ ));
+ $expected = array(
+ 'Content-Type: text/x-test-b',
+ 'X-Test-B: bar',
+ 'X-Test-A: foo'
+ );
+ $this->assertEqual($expected, $response->headers());
+ }
+
public function testEmptyResponse() {
$response = new Response(array('message' => "\n"));
$result = trim((string) $response);
@@ -161,6 +201,54 @@ function testToString() {
$this->assertEqual($expected, (string) $response);
}
+ public function testToStringDoesNotAddContentTypeHeaderOnTextHtml() {
+ $response = new Response();
+
+ $expected = "HTTP/1.1 200 OK\r\n\r\n\r\n";
+ $result = (string) $response;
+ $this->assertEqual($expected, $result);
+
+ $response = new Response();
+ $response->type('text/html');
+
+ $expected = "HTTP/1.1 200 OK\r\n\r\n\r\n";
+ $result = (string) $response;
+ $this->assertEqual($expected, $result);
+
+ $response = new Response();
+ $response->type('text/plain');
+
+ $expected = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n";
+ $result = (string) $response;
+ $this->assertEqual($expected, $result);
+ }
+
+ public function testToStringTypeAlwaysUsesContentTypeHeader() {
+ $response = new Response();
+ $response->headers('Content-Type', 'text/html');
+
+ $expected = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
+ $result = (string) $response;
+ $this->assertEqual($expected, $result);
+
+ $response = new Response();
+ $response->headers('Content-Type', 'text/plain');
+
+ $expected = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n";
+ $result = (string) $response;
+ $this->assertEqual($expected, $result);
+ }
+
+ public function testToStringPrefersHeadersContentTypeOverType() {
+ $response = new Response();
+ $response->headers('Content-Type', 'text/x-test-a');
+ $response->type('text/x-test-b');
+
+ $expected = "HTTP/1.1 200 OK\r\nContent-Type: text/x-test-a\r\n\r\n";
+ $result = (string) $response;
+ $this->assertEqual($expected, $result);
+ }
+
function testTransferEncodingChunkedDecode() {
$headers = join("\r\n", array(
'HTTP/1.1 200 OK',
@@ -221,6 +309,21 @@ function testTransferEncodingChunkedDecode() {
$this->assertEqual($expected, $result);
}
+ public function testTypePriority() {
+ $response = new Response(array(
+ 'message' => "Content-type: text/x-test-a\r\n\r\nfoo",
+ 'type' => 'text/x-test-b',
+ 'headers' => array('Content-Type' => 'text/x-test-c')
+ ));
+ $this->assertEqual('text/x-test-c', $response->type());
+
+ $response = new Response(array(
+ 'message' => "Content-type: text/x-test-a\r\n\r\nfoo",
+ 'type' => 'text/x-test-b'
+ ));
+ $this->assertEqual('text/x-test-b', $response->type());
+ }
+
public function testTypeHeader() {
$response = new Response(array('type' => 'application/json'));
$result = (string) $response;

0 comments on commit 854171d

Please sign in to comment.
Something went wrong with that request. Please try again.