Overview

Namespaces

  • Peach
    • DF
    • DT
    • Http
      • Body
      • Header
    • Markup
    • Util

Classes

  • Peach\DF\Base64Codec
  • Peach\DF\CodecChain
  • Peach\DF\JsonCodec
  • Peach\DF\SerializationCodec
  • Peach\DF\Utf8Codec
  • Peach\DT\AbstractTime
  • Peach\DT\Clock
  • Peach\DT\Date
  • Peach\DT\Datetime
  • Peach\DT\DefaultClock
  • Peach\DT\FixedClock
  • Peach\DT\FormatWrapper
  • Peach\DT\HttpDateFormat
  • Peach\DT\OffsetClock
  • Peach\DT\ShiftFormat
  • Peach\DT\SimpleFormat
  • Peach\DT\TimeEquator
  • Peach\DT\Timestamp
  • Peach\DT\TimeWrapper
  • Peach\DT\UnixTimeFormat
  • Peach\DT\Util
  • Peach\DT\W3cDatetimeFormat
  • Peach\Http\Body
  • Peach\Http\Body\CodecRenderer
  • Peach\Http\Body\StringRenderer
  • Peach\Http\DefaultEndpoint
  • Peach\Http\Header\CookieItem
  • Peach\Http\Header\CookieOptions
  • Peach\Http\Header\HttpDate
  • Peach\Http\Header\NoField
  • Peach\Http\Header\QualityValues
  • Peach\Http\Header\Raw
  • Peach\Http\Header\SetCookie
  • Peach\Http\Header\Status
  • Peach\Http\Request
  • Peach\Http\Response
  • Peach\Http\Util
  • Peach\Markup\AbstractHelper
  • Peach\Markup\AbstractRenderer
  • Peach\Markup\BaseHelper
  • Peach\Markup\BreakControlWrapper
  • Peach\Markup\Builder
  • Peach\Markup\Code
  • Peach\Markup\Comment
  • Peach\Markup\ContainerElement
  • Peach\Markup\Context
  • Peach\Markup\DebugBuilder
  • Peach\Markup\DebugContext
  • Peach\Markup\DefaultBreakControl
  • Peach\Markup\DefaultBuilder
  • Peach\Markup\DefaultContext
  • Peach\Markup\Element
  • Peach\Markup\EmptyElement
  • Peach\Markup\HelperObject
  • Peach\Markup\HtmlHelper
  • Peach\Markup\Indent
  • Peach\Markup\MinimalBreakControl
  • Peach\Markup\NameBreakControl
  • Peach\Markup\NameValidator
  • Peach\Markup\NodeList
  • Peach\Markup\None
  • Peach\Markup\SgmlRenderer
  • Peach\Markup\Text
  • Peach\Markup\XmlRenderer
  • Peach\Util\AbstractMapEntry
  • Peach\Util\ArrayMap
  • Peach\Util\ArrayMapEntry
  • Peach\Util\Arrays
  • Peach\Util\DefaultComparator
  • Peach\Util\DefaultEquator
  • Peach\Util\HashMap
  • Peach\Util\HashMapEntry
  • Peach\Util\Strings
  • Peach\Util\Values

Interfaces

  • Peach\DF\Codec
  • Peach\DT\Format
  • Peach\DT\Time
  • Peach\Http\BodyRenderer
  • Peach\Http\Endpoint
  • Peach\Http\HeaderField
  • Peach\Http\MultiHeaderField
  • Peach\Http\SingleHeaderField
  • Peach\Markup\BreakControl
  • Peach\Markup\Component
  • Peach\Markup\Container
  • Peach\Markup\Helper
  • Peach\Markup\Node
  • Peach\Markup\Renderer
  • Peach\Util\Comparable
  • Peach\Util\Comparator
  • Peach\Util\Equator
  • Peach\Util\Map
  • Peach\Util\MapEntry
  • Overview
  • Namespace
  • Class
  1: <?php
  2: /*
  3:  * Copyright (c) 2015 @trashtoy
  4:  * https://github.com/trashtoy/
  5:  * 
  6:  * Permission is hereby granted, free of charge, to any person obtaining a copy of
  7:  * this software and associated documentation files (the "Software"), to deal in
  8:  * the Software without restriction, including without limitation the rights to use,
  9:  * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 10:  * Software, and to permit persons to whom the Software is furnished to do so,
 11:  * subject to the following conditions:
 12:  * 
 13:  * The above copyright notice and this permission notice shall be included in all
 14:  * copies or substantial portions of the Software.
 15:  * 
 16:  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17:  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 18:  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 19:  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 20:  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 21:  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 22:  */
 23: /**
 24:  * PHP class file.
 25:  * @auhtor trashtoy
 26:  * @since  2.2.0
 27:  */
 28: namespace Peach\Http;
 29: 
 30: use InvalidArgumentException;
 31: use Peach\DT\HttpDateFormat;
 32: use Peach\DT\Timestamp;
 33: use Peach\Http\Header\HttpDate;
 34: use Peach\Http\Header\QualityValues;
 35: use Peach\Http\Header\Raw;
 36: 
 37: /**
 38:  * HTTP メッセージを扱う際によく使われる機能を集めたユーティリティクラスです.
 39:  */
 40: class Util
 41: {
 42:     /**
 43:      * このクラスはインスタンス化できません
 44:      */
 45:     private function __construct() {}
 46:     
 47:     /**
 48:      * 指定された文字列が HTTP ヘッダー名として妥当かどうかを検証します.
 49:      * 文字列が半角アルファベット・数字・ハイフンから成る場合のみ妥当とします.
 50:      * 妥当な文字列でない場合は InvalidArgumentException をスローします.
 51:      * 
 52:      * @param string $name ヘッダー名
 53:      * @throws InvalidArgumentException 引数がヘッダー名として不正だった場合
 54:      */
 55:     public static function validateHeaderName($name)
 56:     {
 57:         // @codeCoverageIgnoreStart
 58:         static $whiteList = array(
 59:             ":authority",
 60:             ":path",
 61:             ":method",
 62:             ":scheme",
 63:             ":status",
 64:         );
 65:         // @codeCoverageIgnoreEnd
 66:         if (in_array($name, $whiteList, true)) {
 67:             return;
 68:         }
 69:         if (!preg_match("/\\A[a-zA-Z0-9\\-]+\\z/", $name)) {
 70:             throw new InvalidArgumentException("{$name} is not a valid header name");
 71:         }
 72:     }
 73:     
 74:     /**
 75:      * 指定された文字列が HTTP ヘッダーの値として妥当かどうかを検証します.
 76:      * 
 77:      * {@link https://tools.ietf.org/html/rfc7230 RFC 7230} で定義された以下の ABNF に基いて妥当性の判定を行います.
 78:      * 
 79:      * <pre>
 80:      * header-field   = field-name ":" OWS field-value OWS
 81:      * 
 82:      * field-name     = token
 83:      * field-value    = *( field-content / obs-fold )
 84:      * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
 85:      * field-vchar    = VCHAR / obs-text
 86:      *
 87:      * obs-fold       = CRLF 1*( SP / HTAB )
 88:      * </pre>
 89:      * 
 90:      * 妥当な文字列でない場合は InvalidArgumentException をスローします.
 91:      * obs-text および obs-fold については RFC7230 で廃止されているため例外として処理します.
 92:      * 
 93:      * @param  string $value 検査対象のヘッダー値
 94:      * @throws InvalidArgumentException 引数がヘッダー値として不正だった場合
 95:      */
 96:     public static function validateHeaderValue($value)
 97:     {
 98:         if (!self::handleValidateHeaderValue($value)) {
 99:             throw new InvalidArgumentException("'{$value}' is not a valid header value");
100:         }
101:     }
102:     
103:     /**
104:      * 引数がヘッダー値として妥当な場合のみ true を返します.
105:      * 
106:      * @param  string $value ヘッダー値
107:      * @return bool          妥当なヘッダー値の場合は true, それ以外は false
108:      */
109:     private static function handleValidateHeaderValue($value)
110:     {
111:         $trimmed = trim($value);
112:         if ($trimmed !== $value) {
113:             return false;
114:         }
115:         if ($value === "") {
116:             return true;
117:         }
118:         $bytes = str_split($value);
119:         return (count($bytes) === 1) ? self::validateVCHAR($value) : self::validateBytes($bytes);
120:     }
121:     
122:     /**
123:      * 指定された文字列が印字可能文字と空白文字で構成されているかどうかを確認します.
124:      * ただし文字列の先頭および末尾が空白文字だった場合は NG とします.
125:      * 
126:      * @param  array $bytes
127:      * @return bool
128:      */
129:     private static function validateBytes($bytes)
130:     {
131:         $head = array_shift($bytes);
132:         if (!self::validateVCHAR($head)) {
133:             return false;
134:         }
135:         $tail = array_pop($bytes);
136:         if (!self::validateVCHAR($tail)) {
137:             return false;
138:         }
139:         foreach ($bytes as $chr) {
140:             if (!self::validateVCHAR($chr) && $chr !== " " && $chr !== "\t") {
141:                 return false;
142:             }
143:         }
144:         return true;
145:     }
146:     
147:     /**
148:      * 引数の 1 文字について, 印字可能文字かどうかを判定します.
149:      * @param  string $chr 検査対象の文字
150:      * @return bool        印字可能文字の場合のみ true
151:      */
152:     private static function validateVCHAR($chr)
153:     {
154:         $byte = ord($chr);
155:         return (0x21 <= $byte && $byte <= 0x7E);
156:     }
157:     
158:     /**
159:      * 指定された Request の If-Modified-Since および If-None-Match ヘッダーを参照し,
160:      * この Request がキャッシュしているリソースの最新版が存在するかどうかを判定します.
161:      * 
162:      * @param  Request   $request      判定対象の Request
163:      * @param  Timestamp $lastModified サーバー側リソースの最終更新日時
164:      * @param  string    $etag         サーバー側リソースの ETag
165:      * @return bool
166:      */
167:     public static function checkResponseUpdate(Request $request, Timestamp $lastModified, $etag = null)
168:     {
169:         $ifModifiedSince = $request->getHeader("If-Modified-Since");
170:         if (!($ifModifiedSince instanceof HttpDate)) {
171:             return true;
172:         }
173:         $clientTime = $ifModifiedSince->getValue();
174:         if ($clientTime->before($lastModified)) {
175:             return true;
176:         }
177:         
178:         return $etag !== $request->getHeader("If-None-Match")->getValue();
179:     }
180:     
181:     /**
182:      * 指定されたヘッダー名, ヘッダー値の組み合わせから
183:      * HeaderField オブジェクトを構築します.
184:      * 
185:      * @param  string $name  ヘッダー名
186:      * @param  string $value ヘッダー値
187:      * @return HeaderField
188:      */
189:     public static function parseHeader($name, $value)
190:     {
191:         static $qNames = array(
192:             "accept",
193:             "accept-language",
194:             "accept-encoding",
195:         );
196:         static $dNames = array(
197:             "date",
198:             "if-modified-since",
199:             "last-modified",
200:         );
201:         $lName = strtolower($name);
202:         if (in_array($lName, $qNames)) {
203:             return new QualityValues($lName, self::parseQualityValue($value));
204:         }
205:         if (in_array($lName, $dNames)) {
206:             $format    = HttpDateFormat::getInstance();
207:             $timestamp = Timestamp::parse($value, $format);
208:             return new HttpDate($lName, $timestamp, $format);
209:         }
210:         if ($lName === "host") {
211:             return new Raw(":authority", $value);
212:         }
213:         
214:         return new Raw($lName, $value);
215:     }
216:     
217:     /**
218:      * qualitiy value を配列に変換します.
219:      * 引数を "ja,en-us;q=0.8,en;q=0.6" とした場合, 返り値は以下の配列になります.
220:      * <pre>
221:      * array("ja" => 1.0, "en-us" => 0.8, "en" => 0.6)
222:      * </pre>
223:      * 
224:      * @param  string $value
225:      * @return array
226:      */
227:     private static function parseQualityValue($value)
228:     {
229:         $values  = preg_split("/\\s*,\\s*/", $value);
230:         $matched = array();
231:         $qvList  = array();
232:         foreach ($values as $item) {
233:             if (preg_match("/\\A([^;]+)\\s*;\\s*(.+)\\z/", $item, $matched)) {
234:                 $key    = $matched[1];
235:                 $qvalue = self::parseQvalue($matched[2]);
236:             } else {
237:                 $key    = $item;
238:                 $qvalue = 1.0;
239:             }
240:             $qvList[$key] = $qvalue;
241:         }
242:         return $qvList;
243:     }
244:     
245:     /**
246:      * qvalue 形式の文字列 ("q=0.8" など) から小数部分を解析し, float として返します.
247:      * 
248:      * @param  string $qvalue "q=0.9" のような形式の文字列
249:      * @return float  qvalue の小数値. もしも不正な場合は 1.0
250:      */
251:     private static function parseQvalue($qvalue)
252:     {
253:         $matched = array();
254:         if (preg_match("/\\Aq\\s*=\\s*([0-9\\.]+)\\z/", $qvalue, $matched)) {
255:             $val = (float) $matched[1];
256:             return (0.0 < $val && $val <= 1.0) ? $val : 1;
257:         }
258:         return 1;
259:     }
260: }
261: 
PEACH2 API documentation generated by ApiGen