Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: yiisoft/yii
base: 5e87516840
...
head fork: yiisoft/yii
compare: 968ff9c0df
  • 20 commits
  • 3 files changed
  • 0 commit comments
  • 2 contributors
Commits on Feb 20, 2013
Rupert-RR Enh #2131: Added Accept header parsing to CHttpRequest to give an array
of accepted types in order of preference
6a864f9
Commits on Mar 14, 2013
Rupert-RR Merge remote-tracking branch 'upstream/master' into
2131-add-accept-header-parsing

Conflicts:
	CHANGELOG
a5b8240
Rupert-RR Moved position of line to follow numerical order. d4f53a8
Rupert-RR Added #135 back in changelog, which got lost somehow.. 2f7a799
Commits on Mar 18, 2013
Rupert-RR Separated out parse and compare functions.
Reduced regular expression count to 2 from 3.
Modified MIME type array map structure.
e3897e8
Rupert-RR Merge remote-tracking branch 'upstream/master' into
2131-add-accept-header-parsing

Conflicts:
	CHANGELOG
67b285d
Rupert-RR Typo corrections 1c0fb69
Commits on Mar 21, 2013
Rupert-RR Altered parseAcceptHeader() to use only one regexp.
Thanks to Ka on StackExchange for this expression.
c255592
Rupert-RR Merge branch 'master' of git://github.com/yiisoft/yii into 2131-add-a…
…ccept-header-parsing

Upstream merge.
8193901
Rupert-RR Typo corrections and code tidy up. 57befee
Rupert-RR parseAcceptHeader() function description tidy up. ae6bc4a
Rupert-RR Added unit test file for CHttpRequest for the methods
parseAcceptHeader() and compareAcceptTypes().
Modified the regexp in parseAcceptHeader() to accept wildcards in the
path.
Modified the description of compareAcceptTypes() to better reflect the
comparison result (higher preference returns lower value, so that most
preferred is first in the array).
2ae1700
Rupert-RR Merge branch 'master' of git://github.com/yiisoft/yii into 2131-add-a…
…ccept-header-parsing

Merge from upstream.
f088486
Rupert-RR Undo accidental permissions change on bootstrap.php be4ff11
Rupert-RR Transferred data for unit tests from the test functions into data
providers.
82d23f8
Rupert-RR Removed unnecessary spaces. f980699
Rupert-RR Yii code style correction 006a893
Commits on Mar 27, 2013
@cebe cebe Merge pull request #2132 branch '2131-add-accept-header-parsing' of h…
…ttps://github.com/Rupert-RR/yii into Rupert-RR-2131-add-accept-header-parsing

* '2131-add-accept-header-parsing' of https://github.com/Rupert-RR/yii:
  Yii code style correction
  Removed unnecessary spaces.
  Transferred data for unit tests from the test functions into data providers.
  Undo accidental permissions change on bootstrap.php
  Added unit test file for CHttpRequest for the methods parseAcceptHeader() and compareAcceptTypes(). Modified the regexp in parseAcceptHeader() to accept wildcards in the path. Modified the description of compareAcceptTypes() to better reflect the comparison result (higher preference returns lower value, so that most preferred is first in the array).
  parseAcceptHeader() function description tidy up.
  Typo corrections and code tidy up.
  Altered parseAcceptHeader() to use only one regexp. Thanks to Ka on StackExchange for this expression.
  Typo corrections
  Separated out parse and compare functions. Reduced regular expression count to 2 from 3. Modified MIME type array map structure.
  Added #135 back in changelog, which got lost somehow..
  Moved position of line to follow numerical order.
  Enh #2131: Added Accept header parsing to CHttpRequest to give an array of accepted types in order of preference

Conflicts:
	CHANGELOG
d27c941
@cebe cebe php doc adjustments after #2132 7d91ceb
@cebe cebe Merge branch 'Rupert-RR-2131-add-accept-header-parsing'
* Rupert-RR-2131-add-accept-header-parsing:
  php doc adjustments after #2132
  Yii code style correction
  Removed unnecessary spaces.
  Transferred data for unit tests from the test functions into data providers.
  Undo accidental permissions change on bootstrap.php
  Added unit test file for CHttpRequest for the methods parseAcceptHeader() and compareAcceptTypes(). Modified the regexp in parseAcceptHeader() to accept wildcards in the path. Modified the description of compareAcceptTypes() to better reflect the comparison result (higher preference returns lower value, so that most preferred is first in the array).
  parseAcceptHeader() function description tidy up.
  Typo corrections and code tidy up.
  Altered parseAcceptHeader() to use only one regexp. Thanks to Ka on StackExchange for this expression.
  Typo corrections
  Separated out parse and compare functions. Reduced regular expression count to 2 from 3. Modified MIME type array map structure.
  Added #135 back in changelog, which got lost somehow..
  Moved position of line to follow numerical order.
  Enh #2131: Added Accept header parsing to CHttpRequest to give an array of accepted types in order of preference
968ff9c
View
1  CHANGELOG
@@ -49,6 +49,7 @@ Version 1.1.14 work in progress
- Enh #2038: CFormatter::formatNtext() method can replace newlines with `<p></p>` not just with `<br />` as it was before (resurtm)
- Enh #2090: Allow passing array of columns to CDbSchema::addPrimaryKey() (paystey)
- Enh #2096: CAPTCHA: non-free Duality.ttf font replaced by open/free SpicyRice.ttf (licensed under SIL OFL v1.1) (resurtm)
+- Enh #2131: Added Accept header parsing to CHttpRequest to give an array of accepted types in order of preference (Rupert-RR)
- Enh #2135: MessageCommand can now handles Yii::t() messages with files in subfolders (firsyura)
- Enh #2205: CActiveForm::error() now depends on CHtml::$errorContainerTag (malyshev)
- Enh #2217: Support of the empty option for CHtml::radioButtonList() has been introduced (resurtm)
View
140 framework/web/CHttpRequest.php
@@ -50,6 +50,8 @@
* @property integer $port Port number for insecure requests.
* @property integer $securePort Port number for secure requests.
* @property CCookieCollection|CHttpCookie[] $cookies The cookie collection.
+ * @property array $preferredAcceptType The user preferred accept type as an array map, e.g. array('type' => 'application', 'subType' => 'xhtml', 'baseType' => 'xml', 'params' => array('q' => 0.9)).
+ * @property array $preferredAcceptTypes An array of all user accepted types (as array maps like array('type' => 'application', 'subType' => 'xhtml', 'baseType' => 'xml', 'params' => array('q' => 0.9)) ) in order of preference.
* @property string $preferredLanguage The user preferred language.
* @property array $preferredLanguages An array of all user accepted languages in order of preference.
* @property string $csrfToken The random token for CSRF validation.
@@ -93,6 +95,7 @@ class CHttpRequest extends CApplicationComponent
private $_hostInfo;
private $_baseUrl;
private $_cookies;
+ private $_preferredAcceptTypes;
private $_preferredLanguages;
private $_csrfToken;
private $_restParams;
@@ -376,6 +379,7 @@ public function setBaseUrl($value)
/**
* Returns the relative URL of the entry script.
* The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
+ * @throws CException when it is unable to determine the entry script URL.
* @return string the relative URL of the entry script.
*/
public function getScriptUrl()
@@ -801,6 +805,142 @@ public function redirect($url,$terminate=true,$statusCode=302)
}
/**
+ * Parses an HTTP Accept header, returning an array map with all parts of each entry.
+ * Each array entry consists of a map with the type, subType, baseType and params, an array map of key-value parameters,
+ * obligatorily including a `q` value (i.e. preference ranking) as a double.
+ * For example, an Accept header value of <code>'application/xhtml+xml;q=0.9;level=1'</code> would give an array entry of
+ * <pre>
+ * array(
+ * 'type' => 'application',
+ * 'subType' => 'xhtml',
+ * 'baseType' => 'xml',
+ * 'params' => array(
+ * 'q' => 0.9,
+ * 'level' => '1',
+ * ),
+ * )
+ * </pre>
+ *
+ * <b>Please note:</b>
+ * To avoid great complexity, there are no steps taken to ensure that quoted strings are treated properly.
+ * If the header text includes quoted strings containing space or the , or ; characters then the results may not be correct!
+ *
+ * See also {@link http://tools.ietf.org/html/rfc2616#section-14.1} for details on Accept header.
+ * @param string $header the accept header value to parse
+ * @return array the user accepted MIME types.
+ */
+ public static function parseAcceptHeader($header)
+ {
+ $matches=array();
+ $accepts=array();
+ // get individual entries with their type, subtype, basetype and params
+ preg_match_all('/(?:\G\s?,\s?|^)(\w+|\*)\/(\w+|\*)(?:\+(\w+))?|(?<!^)\G(?:\s?;\s?(\w+)=([\w\.]+))/',$header,$matches);
+ // the regexp should (in theory) always return an array of 6 arrays
+ if(count($matches)===6)
+ {
+ $i=0;
+ $itemLen=count($matches[1]);
+ while($i<$itemLen)
+ {
+ // fill out a content type
+ $accept=array(
+ 'type'=>$matches[1][$i],
+ 'subType'=>$matches[2][$i],
+ 'baseType'=>null,
+ 'params'=>array(),
+ );
+ // fill in the base type if it exists
+ if($matches[3][$i]!==null && $matches[3][$i]!=='')
+ $accept['baseType']=$matches[3][$i];
+ // continue looping while there is no new content type, to fill in all accompanying params
+ for($i++;$i<$itemLen;$i++)
+ {
+ // if the next content type is null, then the item is a param for the current content type
+ if($matches[1][$i]===null || $matches[1][$i]==='')
+ {
+ // if this is the quality param, convert it to a double
+ if($matches[4][$i]==='q')
+ {
+ // sanity check on q value
+ $q=(double)$matches[5][$i];
+ if($q>1)
+ $q=(double)1;
+ elseif($q<0)
+ $q=(double)0;
+ $accept['params'][$matches[4][$i]]=$q;
+ }
+ else
+ $accept['params'][$matches[4][$i]]=$matches[5][$i];
+ }
+ else
+ break;
+ }
+ // q defaults to 1 if not explicitly given
+ if(!isset($accept['params']['q']))
+ $accept['params']['q']=(double)1;
+ $accepts[] = $accept;
+ }
+ }
+ return $accepts;
+ }
+
+ /**
+ * Compare function for determining the preference of accepted MIME type array maps
+ * See {@link parseAcceptHeader()} for the format of $a and $b
+ * @param array $a user accepted MIME type as an array map
+ * @param array $b user accepted MIME type as an array map
+ * @return integer -1, 0 or 1 if $a has respectively greater preference, equal preference or less preference than $b (higher preference comes first).
+ */
+ public static function compareAcceptTypes($a,$b)
+ {
+ // check for equal quality first
+ if($a['params']['q']===$b['params']['q'])
+ if(!($a['type']==='*' xor $b['type']==='*'))
+ if (!($a['subType']==='*' xor $b['subType']==='*'))
+ // finally, higher number of parameters counts as greater precedence
+ if(count($a['params'])===count($b['params']))
+ return 0;
+ else
+ return count($a['params'])<count($b['params']) ? 1 : -1;
+ // more specific takes precedence - whichever one doesn't have a * subType
+ else
+ return $a['subType']==='*' ? 1 : -1;
+ // more specific takes precedence - whichever one doesn't have a * type
+ else
+ return $a['type']==='*' ? 1 : -1;
+ else
+ return ($a['params']['q']<$b['params']['q']) ? 1 : -1;
+ }
+
+ /**
+ * Returns an array of user accepted MIME types in order of preference.
+ * Each array entry consists of a map with the type, subType, baseType and params, an array map of key-value parameters.
+ * See {@link parseAcceptHeader()} for a description of the array map.
+ * @return array the user accepted MIME types, as array maps, in the order of preference.
+ */
+ public function getPreferredAcceptTypes()
+ {
+ if($this->_preferredAcceptTypes===null)
+ {
+ $accepts=self::parseAcceptHeader($this->getAcceptTypes());
+ usort($accepts,array(get_class($this),'compareAcceptTypes'));
+ $this->_preferredAcceptTypes=$accepts;
+ }
+ return $this->_preferredAcceptTypes;
+ }
+
+ /**
+ * Returns the user preferred accept MIME type.
+ * The MIME type is returned as an array map (see {@link parseAcceptHeader()}).
+ * @return array the user preferred accept MIME type or false if the user does not have any.
+ */
+ public function getPreferredAcceptType()
+ {
+ $preferredAcceptTypes=$this->getPreferredAcceptTypes();
+ return empty($preferredAcceptTypes) ? false : $preferredAcceptTypes[0];
+ }
+
+ /**
* Returns an array of user accepted languages in order of preference.
* The returned language IDs will NOT be canonicalized using {@link CLocale::getCanonicalID}.
* @return array the user accepted languages in the order of preference.
View
273 tests/framework/web/CHttpRequestTest.php
@@ -0,0 +1,273 @@
+<?php
+
+class CHttpRequestTest extends CTestCase
+{
+ /**
+ * @covers CHttpRequest::parseAcceptHeader
+ * @dataProvider acceptHeaderDataProvider
+ */
+ public function testParseAcceptHeader($header,$result,$errorString='Parse of header did not give expected result')
+ {
+ $this->assertEquals($result,CHttpRequest::parseAcceptHeader($header),$errorString);
+ }
+
+ /**
+ * @covers CHttpRequest::compareAcceptTypes
+ * @dataProvider acceptContentTypeArrayMapDataProvider
+ */
+ public function testCompareAcceptTypes($a,$b,$result,$errorString='Compare of content type array maps did not give expected preference')
+ {
+ $this->assertEquals($result,CHttpRequest::compareAcceptTypes($a,$b),$errorString);
+ // make sure that inverse comparison holds
+ $this->assertEquals($result*-1,CHttpRequest::compareAcceptTypes($b,$a),'(Inverse) '.$errorString);
+ }
+
+ public function acceptHeaderDataProvider()
+ {
+ return array(
+ // null header
+ array(
+ null,
+ array(),
+ 'Parsing null Accept header did not return empty array',
+ ),
+ // empty header
+ array(
+ '',
+ array(),
+ 'Parsing empty Accept header did not return empty array',
+ ),
+ // nonsense header, containing no valid accept types (but containing the characters that the header is split on)
+ array(
+ 'gsf,\'yas\'erys"rt;,";s,y s;,',
+ array(),
+ 'Parsing completely invalid Accept header did not return empty array',
+ ),
+ // valid header containing only content types
+ array(
+ 'application/xhtml+xml,text/html,*/json,image/png',
+ array(
+ array(
+ 'type'=>'application',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>1,
+ ),
+ ),
+ array(
+ 'type'=>'text',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ ),
+ ),
+ array(
+ 'type'=>'*',
+ 'subType'=>'json',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ ),
+ ),
+ array(
+ 'type'=>'image',
+ 'subType'=>'png',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ ),
+ ),
+ ),
+ 'Parsing valid Accept header containing only content types did not return correct result',
+ ),
+ // valid header containing all details
+ array(
+ 'application/xhtml+xml;q=0.9,text/html,*/json;q=4;level=three,image/png;a=1;b=2;c=3',
+ array(
+ array(
+ 'type'=>'application',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.9,
+ ),
+ ),
+ array(
+ 'type'=>'text',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ ),
+ ),
+ array(
+ 'type'=>'*',
+ 'subType'=>'json',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ 'level'=>'three',
+ ),
+ ),
+ array(
+ 'type'=>'image',
+ 'subType'=>'png',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ 'a'=>1,
+ 'b'=>2,
+ 'c'=>3,
+ ),
+ ),
+ ),
+ 'Parsing valid Accept header containing all details did not return correct result',
+ ),
+ // partially valid header containing all details (no , after */json)
+ array(
+ 'application/xhtml+xml;q=0.9,text/html,*/json;q=4;level=three image/png;a=1;b=2;c=3',
+ array(
+ array(
+ 'type'=>'application',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.9,
+ ),
+ ),
+ array(
+ 'type'=>'text',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ ),
+ ),
+ array(
+ 'type'=>'*',
+ 'subType'=>'json',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>1,
+ 'level'=>'three',
+ ),
+ ),
+ ),
+ 'Parsing partially valid Accept header containing all details did not return correct result',
+ ),
+ );
+ }
+
+ public function acceptContentTypeArrayMapDataProvider()
+ {
+ return array(
+ array(
+ array(
+ 'type'=>'application',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.99,
+ ),
+ ),
+ array(
+ 'type'=>'text',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>(double)1,
+ ),
+ ),
+ 1,
+ 'Comparing different q did not assign correct preference',
+ ),
+ array(
+ array(
+ 'type'=>'application',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.5,
+ ),
+ ),
+ array(
+ 'type'=>'*',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>0.5,
+ ),
+ ),
+ -1,
+ 'Comparing type wildcard with specific type did not assign correct preference',
+ ),
+ array(
+ array(
+ 'type'=>'application',
+ 'subType'=>'*',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.5,
+ ),
+ ),
+ array(
+ 'type'=>'text',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>0.5,
+ ),
+ ),
+ 1,
+ 'Comparing subType wildcard with specific subType did not assign correct preference',
+ ),
+ array(
+ array(
+ 'type'=>'*',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.9,
+ 'foo'=>'bar2',
+ ),
+ ),
+ array(
+ 'type'=>'*',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>0.9,
+ 'foo'=>'bar',
+ 'test'=>'drive',
+ ),
+ ),
+ 1,
+ 'Comparing different number of params did not assign correct preference',
+ ),
+ array(
+ array(
+ 'type'=>'*',
+ 'subType'=>'xhtml',
+ 'baseType'=>'xml',
+ 'params'=>array(
+ 'q'=>0.9,
+ 'foo'=>'bar',
+ ),
+ ),
+ array(
+ 'type'=>'*',
+ 'subType'=>'html',
+ 'baseType'=>null,
+ 'params'=>array(
+ 'q'=>0.9,
+ 'foo'=>'bar',
+ ),
+ ),
+ 0,
+ 'Comparing equal type, subType, q and number of params did not return equality',
+ ),
+ );
+ }
+}

No commit comments for this range

Something went wrong with that request. Please try again.