├── .idea ├── .name ├── encodings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── vcs.xml ├── wechat_deleted_friends.iml └── workspace.xml ├── README.md ├── WeChat.php ├── httpUtils ├── Requests.php └── Requests │ ├── Auth.php │ ├── Auth │ └── Basic.php │ ├── Cookie.php │ ├── Cookie │ └── Jar.php │ ├── Exception.php │ ├── Exception │ ├── HTTP.php │ └── HTTP │ │ ├── 400.php │ │ ├── 401.php │ │ ├── 402.php │ │ ├── 403.php │ │ ├── 404.php │ │ ├── 405.php │ │ ├── 406.php │ │ ├── 407.php │ │ ├── 408.php │ │ ├── 409.php │ │ ├── 410.php │ │ ├── 411.php │ │ ├── 412.php │ │ ├── 413.php │ │ ├── 414.php │ │ ├── 415.php │ │ ├── 416.php │ │ ├── 417.php │ │ ├── 418.php │ │ ├── 428.php │ │ ├── 429.php │ │ ├── 431.php │ │ ├── 500.php │ │ ├── 501.php │ │ ├── 502.php │ │ ├── 503.php │ │ ├── 504.php │ │ ├── 505.php │ │ ├── 511.php │ │ └── Unknown.php │ ├── Hooker.php │ ├── Hooks.php │ ├── IDNAEncoder.php │ ├── IPv6.php │ ├── IRI.php │ ├── Proxy.php │ ├── Proxy │ └── HTTP.php │ ├── Response.php │ ├── Response │ └── Headers.php │ ├── SSL.php │ ├── Session.php │ ├── Transport.php │ ├── Transport │ ├── cURL.php │ ├── cacert.pem │ └── fsockopen.php │ └── Utility │ ├── CaseInsensitiveDictionary.php │ └── FilteredIterator.php └── index.php /.idea/.name: -------------------------------------------------------------------------------- 1 | wechat_deleted_friends -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | CSS 12 | 13 | 14 | Code style issuesCSS 15 | 16 | 17 | Invalid elementsCSS 18 | 19 | 20 | 21 | 22 | CSS 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/wechat_deleted_friends.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | true 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 107 | 108 | 109 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 1452221001403 133 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #很多接口已经失效,该项目目前是无法使用的.等我这几天重新写一个更智能点的。 -- 2017-02-10 2 | # wechat_deleted_friends 3 | ###使用方法 4 | ```bash 5 | git clone https://github.com/hundredlee/wechat_deleted_friends.git 6 | 7 | ``` 8 | 9 | ```bash 10 | 11 | php -S "localhost:80" 12 | 13 | ``` 14 | 15 | ###然后在浏览器中访问 localhost:80 即可 16 | 17 | ###建议在php5.6以上的环境运行 18 | -------------------------------------------------------------------------------- /WeChat.php: -------------------------------------------------------------------------------- 1 | 'wx782c26e4c19acffb', 54 | 'fun' => 'new', 55 | 'lang' => 'zh_CN', 56 | '_' => time() 57 | ); 58 | 59 | $responseData = Requests::post('https://login.weixin.qq.com/jslogin', null, $param); 60 | 61 | $body = $responseData->body; 62 | preg_match_all('/window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"/', $body, $array); 63 | 64 | //status code 65 | $statusCode = $array[1][0]; 66 | $uuid = $array[2][0]; 67 | 68 | 69 | if ($statusCode == 200) { 70 | 71 | self::$uuid = $uuid; 72 | 73 | return true; 74 | 75 | } 76 | 77 | return false; 78 | 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function getQRCode() 85 | { 86 | $params = array( 87 | 't' => 'index', 88 | '_' => time() 89 | ); 90 | 91 | $url = 'https://login.weixin.qq.com/qrcode/' . self::$uuid; 92 | 93 | $responseData = Requests::post($url, null, $params); 94 | 95 | self::$tip = 1; 96 | 97 | $src = 'data:image/gif;base64,' . base64_encode($responseData->body); 98 | 99 | $imgLabel = ''; 100 | 101 | return $imgLabel; 102 | 103 | } 104 | 105 | /** 106 | * @return int 107 | */ 108 | public function waitForLogin() 109 | { 110 | $url = sprintf('https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' 111 | , self::$tip, self::$uuid, time()); 112 | 113 | $responseData = Requests::get($url); 114 | 115 | preg_match('/window.code=(\d+)/', $responseData->body, $statusCodeArray); 116 | 117 | //print_r($statusCodeArray); 118 | 119 | $statusCode = (int)$statusCodeArray[1]; 120 | 121 | if ($statusCode == 201) { 122 | 123 | echo '扫描成功,请在手机上面点登录 :)
'; 124 | self::$tip = 0; 125 | 126 | } else if ($statusCode == 200) { 127 | 128 | echo '正在登录,请稍后......'; 129 | 130 | preg_match('/window.redirect_uri="(\S+?)"/', $responseData->body, $responseArray); 131 | 132 | self::$redirect_uri = $responseArray[1] . '&fun=new'; 133 | self::$base_uri = substr(self::$redirect_uri, 0, strrpos(self::$redirect_uri, '/')); 134 | //echo self::$redirect_uri.'
'.self::$base_uri; 135 | 136 | } else if ($statusCode == 408) { 137 | 138 | exit('登录超时!请刷新页面重新扫描二维码......'); 139 | 140 | } 141 | 142 | return $statusCode; 143 | } 144 | 145 | public function login() 146 | { 147 | 148 | $responseData = Requests::get(self::$redirect_uri, null); 149 | 150 | $xmlData = $responseData->body; 151 | 152 | // 153 | //0 154 | //OK 155 | //@crypt_7dd9baa8_b539032f0b7d2a56385e98018735aa39 156 | //1ya64xtGW2Aa7rmS 157 | //2432628783 158 | //njePr%2BqGGxpoiuX%2BBqnolnmUwwJar1YQBcBhHDowzLh1NWsev1%2BMXSWQtoXZBo7p 159 | //1 160 | // 161 | 162 | $xml = simplexml_load_string($xmlData); 163 | 164 | $skeyArray = (array)($xml->skey); 165 | $wxsidArray = (array)($xml->wxsid); 166 | $pass_ticket = $xml->pass_ticket; 167 | 168 | self::$skey = $skeyArray[0]; 169 | self::$wxsid = $wxsidArray[0]; 170 | self::$wxuin = $xml->wxuin; 171 | self::$pass_ticket = $pass_ticket; 172 | 173 | if (self::$skey == '' && self::$skey == '' 174 | && self::$wxuin == '' && self::$pass_ticket == '' 175 | ) { 176 | 177 | return fasle; 178 | 179 | } 180 | 181 | self::$cookie = $responseData->cookies; 182 | 183 | self::$baseRequest['Uin'] = (int)self::$wxuin; 184 | self::$baseRequest['Sid'] = self::$wxsid; 185 | self::$baseRequest['Skey'] = self::$skey; 186 | self::$baseRequest['DeviceId'] = self::$deviceId; 187 | 188 | return true; 189 | 190 | } 191 | 192 | public function weChatInitial() 193 | { 194 | 195 | //print_r(self::$baseRequest); 196 | 197 | 198 | $url = sprintf(self::$base_uri . '/webwxinit?pass_ticket=%s&skey=%s&r=%s', self::$pass_ticket, self::$skey, time()); 199 | 200 | $params = array( 201 | 'BaseRequest' => self::$baseRequest 202 | ); 203 | 204 | $responseData = Requests::post($url, 205 | array( 206 | 'ContentType' => 'application/json; charset=UTF-8', 207 | ), 208 | json_encode($params)); 209 | 210 | 211 | $dictionary = json_decode($responseData->body, 1); 212 | 213 | //print_r($dictionary); 214 | 215 | self::$contactList = $dictionary['ContactList']; 216 | self::$my = $dictionary['User']; 217 | 218 | $errorMsg = $dictionary['BaseResponse']['ErrMsg']; 219 | 220 | if (strlen($errorMsg) > 0) { 221 | echo $errorMsg; 222 | } 223 | 224 | $ret = $dictionary['BaseResponse']['Ret']; 225 | 226 | if ($ret != 0) { 227 | 228 | return fasle; 229 | 230 | } 231 | return true; 232 | 233 | } 234 | 235 | public function webwxgetcontact() 236 | { 237 | 238 | $url = sprintf(self::$base_uri . '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s', self::$pass_ticket, (self::$skey), time()); 239 | 240 | $responseData = Requests::post($url, array('ContentType' => 'application/json; charset=UTF-8'), array(), array('cookies' => self::$cookie)); 241 | 242 | $dictionary = json_decode($responseData->body, 1); 243 | 244 | $memberList = $dictionary['MemberList']; 245 | 246 | $specialUsers = array("newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"); 247 | 248 | foreach ($memberList as $key => $value) { 249 | 250 | if ((trim($value['VerifyFlag']) & 8) != 0) { 251 | 252 | unset($memberList[$key]); 253 | 254 | } 255 | 256 | if (in_array(trim($value['UserName']), $specialUsers)) { 257 | 258 | unset($memberList[$key]); 259 | 260 | } 261 | 262 | if (trim($value['UserName']) == self::$my['UserName']) { 263 | 264 | unset($memberList[$key]); 265 | 266 | } 267 | 268 | if (strpos(trim($value['UserName']), '@@') !== false) { 269 | 270 | unset($memberList[$key]); 271 | 272 | } 273 | 274 | } 275 | 276 | //print_r($memberList); 277 | 278 | return $memberList; 279 | } 280 | 281 | public function createChatRoom($usernames = array()) 282 | { 283 | $usernamesList = array(); 284 | foreach ($usernames as $key => $value) { 285 | 286 | unset($usernames[$key]); 287 | 288 | $usernamesList[]['UserName'] = $value; 289 | } 290 | 291 | $url = sprintf(self::$base_uri . '/webwxcreatechatroom?pass_ticket=%s&r=%s', self::$pass_ticket, time()); 292 | 293 | $params = array( 294 | 'BaseRequest' => self::$baseRequest, 295 | 'MemberCount' => count($usernamesList), 296 | 'MemberList' => $usernamesList, 297 | 'Topic' => '' 298 | ); 299 | 300 | $responseData = Requests::post($url, 301 | array('ContentType' => 'application/json; charset=UTF-8'), 302 | json_encode($params), 303 | array('cookies' => self::$cookie) 304 | ); 305 | 306 | $dictionary = json_decode($responseData->body, 1); 307 | 308 | self::$chatRoomName = $dictionary['ChatRoomName']; 309 | 310 | $memberList = $dictionary['MemberList']; 311 | 312 | foreach ($memberList as $key => $member) { 313 | 314 | if ($member['MemberStatus'] == 4) { 315 | 316 | self::$deleteList[] = $member['UserName']; 317 | 318 | } 319 | 320 | } 321 | 322 | if (strlen($dictionary['BaseResponse']['ErrMsg']) > 0) { 323 | 324 | echo $dictionary['BaseResponse']['ErrMsg'] . '
'; 325 | 326 | } 327 | 328 | return self::$chatRoomName; 329 | } 330 | 331 | public function addMember($chatRoomName, $usernames) 332 | { 333 | 334 | $url = sprintf(self::$base_uri . '/webwxupdatechatroom?fun=addmember&pass_ticket=%s', self::$pass_ticket); 335 | 336 | $params = array( 337 | 'BaseRequest' => self::$baseRequest, 338 | 'ChatRoomName' => $chatRoomName, 339 | 'AddMemberList' => join(',', $usernames) 340 | ); 341 | 342 | $responseData = Requests::post($url, 343 | array('ContentType' => 'application/json; charset=UTF-8'), 344 | json_encode($params), 345 | array('cookies' => self::$cookie) 346 | ); 347 | 348 | $dictionary = json_decode($responseData->body, 1); 349 | 350 | $memberList = $dictionary['MemberList']; 351 | 352 | 353 | foreach ($memberList as $key => $member) { 354 | 355 | if ($member['MemberStatus'] == 4) { 356 | 357 | self::$deleteList[] = $member['UserName']; 358 | 359 | } 360 | 361 | } 362 | 363 | if (strlen($dictionary['BaseResponse']['ErrMsg']) > 0) { 364 | 365 | echo $dictionary['BaseResponse']['ErrMsg'] . '
'; 366 | 367 | } 368 | 369 | return true; 370 | 371 | } 372 | 373 | public function deleteMember($chatRoomName, $usernames) 374 | { 375 | 376 | $url = sprintf(self::$base_uri . '/webwxupdatechatroom?fun=delmember&pass_ticket=%s', self::$pass_ticket); 377 | 378 | $params = array( 379 | 'BaseRequest' => self::$baseRequest, 380 | 'ChatRoomName' => $chatRoomName, 381 | 'DelMemberList' => join(',', $usernames) 382 | ); 383 | 384 | $responseData = Requests::post($url, 385 | array('ContentType' => 'application/json; charset=UTF-8'), 386 | json_encode($params), 387 | array('cookies' => self::$cookie) 388 | ); 389 | 390 | $dictionary = json_decode($responseData->body, 1); 391 | 392 | if (strlen($dictionary['BaseResponse']['ErrMsg']) > 0) { 393 | 394 | echo $dictionary['BaseResponse']['ErrMsg'] . '
'; 395 | 396 | } 397 | 398 | $ret = $dictionary['BaseResponse']['Ret']; 399 | if ($ret != 0) { 400 | 401 | return fasle; 402 | 403 | } 404 | 405 | return true; 406 | } 407 | 408 | public function getDeleteList() 409 | { 410 | return self::$deleteList; 411 | } 412 | } -------------------------------------------------------------------------------- /httpUtils/Requests.php: -------------------------------------------------------------------------------- 1 | dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options)); 306 | 307 | if (!empty($options['transport'])) { 308 | $transport = $options['transport']; 309 | 310 | if (is_string($options['transport'])) { 311 | $transport = new $transport(); 312 | } 313 | } 314 | else { 315 | $transport = self::get_transport(); 316 | } 317 | $response = $transport->request($url, $headers, $data, $options); 318 | 319 | $options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options)); 320 | 321 | return self::parse_response($response, $url, $headers, $data, $options); 322 | } 323 | 324 | /** 325 | * Send multiple HTTP requests simultaneously 326 | * 327 | * The `$requests` parameter takes an associative or indexed array of 328 | * request fields. The key of each request can be used to match up the 329 | * request with the returned data, or with the request passed into your 330 | * `multiple.request.complete` callback. 331 | * 332 | * The request fields value is an associative array with the following keys: 333 | * 334 | * - `url`: Request URL Same as the `$url` parameter to 335 | * {@see Requests::request} 336 | * (string, required) 337 | * - `headers`: Associative array of header fields. Same as the `$headers` 338 | * parameter to {@see Requests::request} 339 | * (array, default: `array()`) 340 | * - `data`: Associative array of data fields or a string. Same as the 341 | * `$data` parameter to {@see Requests::request} 342 | * (array|string, default: `array()`) 343 | * - `type`: HTTP request type (use Requests constants). Same as the `$type` 344 | * parameter to {@see Requests::request} 345 | * (string, default: `Requests::GET`) 346 | * - `data`: Associative array of options. Same as the `$options` parameter 347 | * to {@see Requests::request} 348 | * (array, default: see {@see Requests::request}) 349 | * - `cookies`: Associative array of cookie name to value, or cookie jar. 350 | * (array|Requests_Cookie_Jar) 351 | * 352 | * If the `$options` parameter is specified, individual requests will 353 | * inherit options from it. This can be used to use a single hooking system, 354 | * or set all the types to `Requests::POST`, for example. 355 | * 356 | * In addition, the `$options` parameter takes the following global options: 357 | * 358 | * - `complete`: A callback for when a request is complete. Takes two 359 | * parameters, a Requests_Response/Requests_Exception reference, and the 360 | * ID from the request array (Note: this can also be overridden on a 361 | * per-request basis, although that's a little silly) 362 | * (callback) 363 | * 364 | * @param array $requests Requests data (see description for more information) 365 | * @param array $options Global and default options (see {@see Requests::request}) 366 | * @return array Responses (either Requests_Response or a Requests_Exception object) 367 | */ 368 | public static function request_multiple($requests, $options = array()) { 369 | $options = array_merge(self::get_default_options(true), $options); 370 | 371 | if (!empty($options['hooks'])) { 372 | $options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple')); 373 | if (!empty($options['complete'])) { 374 | $options['hooks']->register('multiple.request.complete', $options['complete']); 375 | } 376 | } 377 | 378 | foreach ($requests as $id => &$request) { 379 | if (!isset($request['headers'])) { 380 | $request['headers'] = array(); 381 | } 382 | if (!isset($request['data'])) { 383 | $request['data'] = array(); 384 | } 385 | if (!isset($request['type'])) { 386 | $request['type'] = self::GET; 387 | } 388 | if (!isset($request['options'])) { 389 | $request['options'] = $options; 390 | $request['options']['type'] = $request['type']; 391 | } 392 | else { 393 | if (empty($request['options']['type'])) { 394 | $request['options']['type'] = $request['type']; 395 | } 396 | $request['options'] = array_merge($options, $request['options']); 397 | } 398 | 399 | self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']); 400 | 401 | // Ensure we only hook in once 402 | if ($request['options']['hooks'] !== $options['hooks']) { 403 | $request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple')); 404 | if (!empty($request['options']['complete'])) { 405 | $request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']); 406 | } 407 | } 408 | } 409 | unset($request); 410 | 411 | if (!empty($options['transport'])) { 412 | $transport = $options['transport']; 413 | 414 | if (is_string($options['transport'])) { 415 | $transport = new $transport(); 416 | } 417 | } 418 | else { 419 | $transport = self::get_transport(); 420 | } 421 | $responses = $transport->request_multiple($requests, $options); 422 | 423 | foreach ($responses as $id => &$response) { 424 | // If our hook got messed with somehow, ensure we end up with the 425 | // correct response 426 | if (is_string($response)) { 427 | $request = $requests[$id]; 428 | self::parse_multiple($response, $request); 429 | $request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id)); 430 | } 431 | } 432 | 433 | return $responses; 434 | } 435 | 436 | /** 437 | * Get the default options 438 | * 439 | * @see Requests::request() for values returned by this method 440 | * @param boolean $multirequest Is this a multirequest? 441 | * @return array Default option values 442 | */ 443 | protected static function get_default_options($multirequest = false) { 444 | $defaults = array( 445 | 'timeout' => 10, 446 | 'useragent' => 'php-requests/' . self::VERSION, 447 | 'redirected' => 0, 448 | 'redirects' => 10, 449 | 'follow_redirects' => true, 450 | 'blocking' => true, 451 | 'type' => self::GET, 452 | 'filename' => false, 453 | 'auth' => false, 454 | 'proxy' => false, 455 | 'cookies' => false, 456 | 'idn' => true, 457 | 'hooks' => null, 458 | 'transport' => null, 459 | 'verify' => dirname( __FILE__ ) . '/Requests/Transport/cacert.pem', 460 | 'verifyname' => true, 461 | ); 462 | if ($multirequest !== false) { 463 | $defaults['complete'] = null; 464 | } 465 | return $defaults; 466 | } 467 | 468 | /** 469 | * Set the default values 470 | * 471 | * @param string $url URL to request 472 | * @param array $headers Extra headers to send with the request 473 | * @param array $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests 474 | * @param string $type HTTP request type 475 | * @param array $options Options for the request 476 | * @return array $options 477 | */ 478 | protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) { 479 | if (!preg_match('/^http(s)?:\/\//i', $url)) { 480 | throw new Requests_Exception('Only HTTP requests are handled.', 'nonhttp', $url); 481 | } 482 | 483 | if (empty($options['hooks'])) { 484 | $options['hooks'] = new Requests_Hooks(); 485 | } 486 | 487 | if (is_array($options['auth'])) { 488 | $options['auth'] = new Requests_Auth_Basic($options['auth']); 489 | } 490 | if ($options['auth'] !== false) { 491 | $options['auth']->register($options['hooks']); 492 | } 493 | 494 | if (!empty($options['proxy'])) { 495 | $options['proxy'] = new Requests_Proxy_HTTP($options['proxy']); 496 | } 497 | if ($options['proxy'] !== false) { 498 | $options['proxy']->register($options['hooks']); 499 | } 500 | 501 | if (is_array($options['cookies'])) { 502 | $options['cookies'] = new Requests_Cookie_Jar($options['cookies']); 503 | } 504 | elseif (empty($options['cookies'])) { 505 | $options['cookies'] = new Requests_Cookie_Jar(); 506 | } 507 | if ($options['cookies'] !== false) { 508 | $options['cookies']->register($options['hooks']); 509 | } 510 | 511 | if ($options['idn'] !== false) { 512 | $iri = new Requests_IRI($url); 513 | $iri->host = Requests_IDNAEncoder::encode($iri->ihost); 514 | $url = $iri->uri; 515 | } 516 | } 517 | 518 | /** 519 | * HTTP response parser 520 | * 521 | * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`) 522 | * @throws Requests_Exception On missing head/body separator (`noversion`) 523 | * @throws Requests_Exception On missing head/body separator (`toomanyredirects`) 524 | * 525 | * @param string $headers Full response text including headers and body 526 | * @param string $url Original request URL 527 | * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects 528 | * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects 529 | * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects 530 | * @return Requests_Response 531 | */ 532 | protected static function parse_response($headers, $url, $req_headers, $req_data, $options) { 533 | $return = new Requests_Response(); 534 | if (!$options['blocking']) { 535 | return $return; 536 | } 537 | 538 | $return->raw = $headers; 539 | $return->url = $url; 540 | 541 | if (!$options['filename']) { 542 | if (($pos = strpos($headers, "\r\n\r\n")) === false) { 543 | // Crap! 544 | throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator'); 545 | } 546 | 547 | $headers = substr($return->raw, 0, $pos); 548 | $return->body = substr($return->raw, $pos + strlen("\n\r\n\r")); 549 | } 550 | else { 551 | $return->body = ''; 552 | } 553 | // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3) 554 | $headers = str_replace("\r\n", "\n", $headers); 555 | // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2) 556 | $headers = preg_replace('/\n[ \t]/', ' ', $headers); 557 | $headers = explode("\n", $headers); 558 | preg_match('#^HTTP/1\.\d[ \t]+(\d+)#i', array_shift($headers), $matches); 559 | if (empty($matches)) { 560 | throw new Requests_Exception('Response could not be parsed', 'noversion', $headers); 561 | } 562 | $return->status_code = (int) $matches[1]; 563 | if ($return->status_code >= 200 && $return->status_code < 300) { 564 | $return->success = true; 565 | } 566 | 567 | foreach ($headers as $header) { 568 | list($key, $value) = explode(':', $header, 2); 569 | $value = trim($value); 570 | preg_replace('#(\s+)#i', ' ', $value); 571 | $return->headers[$key] = $value; 572 | } 573 | if (isset($return->headers['transfer-encoding'])) { 574 | $return->body = self::decode_chunked($return->body); 575 | unset($return->headers['transfer-encoding']); 576 | } 577 | if (isset($return->headers['content-encoding'])) { 578 | $return->body = self::decompress($return->body); 579 | } 580 | 581 | //fsockopen and cURL compatibility 582 | if (isset($return->headers['connection'])) { 583 | unset($return->headers['connection']); 584 | } 585 | 586 | $options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options)); 587 | 588 | if ((in_array($return->status_code, array(300, 301, 302, 303, 307)) || $return->status_code > 307 && $return->status_code < 400) && $options['follow_redirects'] === true) { 589 | if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) { 590 | if ($return->status_code === 303) { 591 | $options['type'] = Requests::GET; 592 | } 593 | $options['redirected']++; 594 | $location = $return->headers['location']; 595 | if (strpos ($location, '/') === 0) { 596 | // relative redirect, for compatibility make it absolute 597 | $location = Requests_IRI::absolutize($url, $location); 598 | $location = $location->uri; 599 | } 600 | $redirected = self::request($location, $req_headers, $req_data, false, $options); 601 | $redirected->history[] = $return; 602 | return $redirected; 603 | } 604 | elseif ($options['redirected'] >= $options['redirects']) { 605 | throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return); 606 | } 607 | } 608 | 609 | $return->redirects = $options['redirected']; 610 | 611 | $options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options)); 612 | return $return; 613 | } 614 | 615 | /** 616 | * Callback for `transport.internal.parse_response` 617 | * 618 | * Internal use only. Converts a raw HTTP response to a Requests_Response 619 | * while still executing a multiple request. 620 | * 621 | * @param string $headers Full response text including headers and body 622 | * @param array $request Request data as passed into {@see Requests::request_multiple()} 623 | * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object 624 | */ 625 | public static function parse_multiple(&$response, $request) { 626 | try { 627 | $response = self::parse_response($response, $request['url'], $request['headers'], $request['data'], $request['options']); 628 | } 629 | catch (Requests_Exception $e) { 630 | $response = $e; 631 | } 632 | } 633 | 634 | /** 635 | * Decoded a chunked body as per RFC 2616 636 | * 637 | * @see http://tools.ietf.org/html/rfc2616#section-3.6.1 638 | * @param string $data Chunked body 639 | * @return string Decoded body 640 | */ 641 | protected static function decode_chunked($data) { 642 | if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) { 643 | return $data; 644 | } 645 | 646 | $decoded = ''; 647 | $encoded = $data; 648 | 649 | while (true) { 650 | $is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches ); 651 | if (!$is_chunked) { 652 | // Looks like it's not chunked after all 653 | return $data; 654 | } 655 | 656 | $length = hexdec(trim($matches[1])); 657 | if ($length === 0) { 658 | // Ignore trailer headers 659 | return $decoded; 660 | } 661 | 662 | $chunk_length = strlen($matches[0]); 663 | $decoded .= $part = substr($encoded, $chunk_length, $length); 664 | $encoded = substr($encoded, $chunk_length + $length + 2); 665 | 666 | if (trim($encoded) === '0' || empty($encoded)) { 667 | return $decoded; 668 | } 669 | } 670 | 671 | // We'll never actually get down here 672 | // @codeCoverageIgnoreStart 673 | } 674 | // @codeCoverageIgnoreEnd 675 | 676 | /** 677 | * Convert a key => value array to a 'key: value' array for headers 678 | * 679 | * @param array $array Dictionary of header values 680 | * @return array List of headers 681 | */ 682 | public static function flatten($array) { 683 | $return = array(); 684 | foreach ($array as $key => $value) { 685 | $return[] = "$key: $value"; 686 | } 687 | return $return; 688 | } 689 | 690 | /** 691 | * Convert a key => value array to a 'key: value' array for headers 692 | * 693 | * @deprecated Misspelling of {@see Requests::flatten} 694 | * @param array $array Dictionary of header values 695 | * @return array List of headers 696 | */ 697 | public static function flattern($array) { 698 | return self::flatten($array); 699 | } 700 | 701 | /** 702 | * Decompress an encoded body 703 | * 704 | * Implements gzip, compress and deflate. Guesses which it is by attempting 705 | * to decode. 706 | * 707 | * @todo Make this smarter by defaulting to whatever the headers say first 708 | * @param string $data Compressed data in one of the above formats 709 | * @return string Decompressed string 710 | */ 711 | public static function decompress($data) { 712 | if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") { 713 | // Not actually compressed. Probably cURL ruining this for us. 714 | return $data; 715 | } 716 | 717 | if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) { 718 | return $decoded; 719 | } 720 | elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) { 721 | return $decoded; 722 | } 723 | elseif (($decoded = self::compatible_gzinflate($data)) !== false) { 724 | return $decoded; 725 | } 726 | elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) { 727 | return $decoded; 728 | } 729 | 730 | return $data; 731 | } 732 | 733 | /** 734 | * Decompression of deflated string while staying compatible with the majority of servers. 735 | * 736 | * Certain Servers will return deflated data with headers which PHP's gzinflate() 737 | * function cannot handle out of the box. The following function has been created from 738 | * various snippets on the gzinflate() PHP documentation. 739 | * 740 | * Warning: Magic numbers within. Due to the potential different formats that the compressed 741 | * data may be returned in, some "magic offsets" are needed to ensure proper decompression 742 | * takes place. For a simple progmatic way to determine the magic offset in use, see: 743 | * http://core.trac.wordpress.org/ticket/18273 744 | * 745 | * @since 2.8.1 746 | * @link http://core.trac.wordpress.org/ticket/18273 747 | * @link http://au2.php.net/manual/en/function.gzinflate.php#70875 748 | * @link http://au2.php.net/manual/en/function.gzinflate.php#77336 749 | * 750 | * @param string $gzData String to decompress. 751 | * @return string|bool False on failure. 752 | */ 753 | public static function compatible_gzinflate($gzData) { 754 | // Compressed data might contain a full zlib header, if so strip it for 755 | // gzinflate() 756 | if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { 757 | $i = 10; 758 | $flg = ord( substr($gzData, 3, 1) ); 759 | if ( $flg > 0 ) { 760 | if ( $flg & 4 ) { 761 | list($xlen) = unpack('v', substr($gzData, $i, 2) ); 762 | $i = $i + 2 + $xlen; 763 | } 764 | if ( $flg & 8 ) 765 | $i = strpos($gzData, "\0", $i) + 1; 766 | if ( $flg & 16 ) 767 | $i = strpos($gzData, "\0", $i) + 1; 768 | if ( $flg & 2 ) 769 | $i = $i + 2; 770 | } 771 | $decompressed = self::compatible_gzinflate( substr( $gzData, $i ) ); 772 | if ( false !== $decompressed ) { 773 | return $decompressed; 774 | } 775 | } 776 | 777 | // If the data is Huffman Encoded, we must first strip the leading 2 778 | // byte Huffman marker for gzinflate() 779 | // The response is Huffman coded by many compressors such as 780 | // java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's 781 | // System.IO.Compression.DeflateStream. 782 | // 783 | // See http://decompres.blogspot.com/ for a quick explanation of this 784 | // data type 785 | $huffman_encoded = false; 786 | 787 | // low nibble of first byte should be 0x08 788 | list( , $first_nibble ) = unpack( 'h', $gzData ); 789 | 790 | // First 2 bytes should be divisible by 0x1F 791 | list( , $first_two_bytes ) = unpack( 'n', $gzData ); 792 | 793 | if ( 0x08 == $first_nibble && 0 == ( $first_two_bytes % 0x1F ) ) 794 | $huffman_encoded = true; 795 | 796 | if ( $huffman_encoded ) { 797 | if ( false !== ( $decompressed = @gzinflate( substr( $gzData, 2 ) ) ) ) 798 | return $decompressed; 799 | } 800 | 801 | if ( "\x50\x4b\x03\x04" == substr( $gzData, 0, 4 ) ) { 802 | // ZIP file format header 803 | // Offset 6: 2 bytes, General-purpose field 804 | // Offset 26: 2 bytes, filename length 805 | // Offset 28: 2 bytes, optional field length 806 | // Offset 30: Filename field, followed by optional field, followed 807 | // immediately by data 808 | list( , $general_purpose_flag ) = unpack( 'v', substr( $gzData, 6, 2 ) ); 809 | 810 | // If the file has been compressed on the fly, 0x08 bit is set of 811 | // the general purpose field. We can use this to differentiate 812 | // between a compressed document, and a ZIP file 813 | $zip_compressed_on_the_fly = ( 0x08 == (0x08 & $general_purpose_flag ) ); 814 | 815 | if ( ! $zip_compressed_on_the_fly ) { 816 | // Don't attempt to decode a compressed zip file 817 | return $gzData; 818 | } 819 | 820 | // Determine the first byte of data, based on the above ZIP header 821 | // offsets: 822 | $first_file_start = array_sum( unpack( 'v2', substr( $gzData, 26, 4 ) ) ); 823 | if ( false !== ( $decompressed = @gzinflate( substr( $gzData, 30 + $first_file_start ) ) ) ) { 824 | return $decompressed; 825 | } 826 | return false; 827 | } 828 | 829 | // Finally fall back to straight gzinflate 830 | if ( false !== ( $decompressed = @gzinflate( $gzData ) ) ) { 831 | return $decompressed; 832 | } 833 | 834 | // Fallback for all above failing, not expected, but included for 835 | // debugging and preventing regressions and to track stats 836 | if ( false !== ( $decompressed = @gzinflate( substr( $gzData, 2 ) ) ) ) { 837 | return $decompressed; 838 | } 839 | 840 | return false; 841 | } 842 | 843 | public static function match_domain($host, $reference) { 844 | // Check for a direct match 845 | if ($host === $reference) { 846 | return true; 847 | } 848 | 849 | // Calculate the valid wildcard match if the host is not an IP address 850 | // Also validates that the host has 3 parts or more, as per Firefox's 851 | // ruleset. 852 | $parts = explode('.', $host); 853 | if (ip2long($host) === false && count($parts) >= 3) { 854 | $parts[0] = '*'; 855 | $wildcard = implode('.', $parts); 856 | if ($wildcard === $reference) { 857 | return true; 858 | } 859 | } 860 | 861 | return false; 862 | } 863 | } 864 | -------------------------------------------------------------------------------- /httpUtils/Requests/Auth.php: -------------------------------------------------------------------------------- 1 | user, $this->pass) = $args; 46 | } 47 | } 48 | 49 | /** 50 | * Register the necessary callbacks 51 | * 52 | * @see curl_before_send 53 | * @see fsockopen_header 54 | * @param Requests_Hooks $hooks Hook system 55 | */ 56 | public function register(Requests_Hooks &$hooks) { 57 | $hooks->register('curl.before_send', array(&$this, 'curl_before_send')); 58 | $hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header')); 59 | } 60 | 61 | /** 62 | * Set cURL parameters before the data is sent 63 | * 64 | * @param resource $handle cURL resource 65 | */ 66 | public function curl_before_send(&$handle) { 67 | curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 68 | curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString()); 69 | } 70 | 71 | /** 72 | * Add extra headers to the request before sending 73 | * 74 | * @param string $out HTTP header string 75 | */ 76 | public function fsockopen_header(&$out) { 77 | $out .= "Authorization: Basic " . base64_encode($this->getAuthString()) . "\r\n"; 78 | } 79 | 80 | /** 81 | * Get the authentication string (user:pass) 82 | * 83 | * @return string 84 | */ 85 | public function getAuthString() { 86 | return $this->user . ':' . $this->pass; 87 | } 88 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Cookie.php: -------------------------------------------------------------------------------- 1 | name = $name; 46 | $this->value = $value; 47 | $this->attributes = $attributes; 48 | } 49 | 50 | /** 51 | * Format a cookie for a Cookie header 52 | * 53 | * This is used when sending cookies to a server. 54 | * 55 | * @return string Cookie formatted for Cookie header 56 | */ 57 | public function formatForHeader() { 58 | return sprintf('%s=%s', $this->name, $this->value); 59 | } 60 | 61 | /** 62 | * Format a cookie for a Set-Cookie header 63 | * 64 | * This is used when sending cookies to clients. This isn't really 65 | * applicable to client-side usage, but might be handy for debugging. 66 | * 67 | * @return string Cookie formatted for Set-Cookie header 68 | */ 69 | public function formatForSetCookie() { 70 | $header_value = $this->formatForHeader(); 71 | if (!empty($this->attributes)) { 72 | $parts = array(); 73 | foreach ($this->attributes as $key => $value) { 74 | // Ignore non-associative attributes 75 | if (is_numeric($key)) { 76 | $parts[] = $value; 77 | } 78 | else { 79 | $parts[] = sprintf('%s=%s', $key, $value); 80 | } 81 | } 82 | 83 | $header_value .= '; ' . implode('; ', $parts); 84 | } 85 | return $header_value; 86 | } 87 | 88 | /** 89 | * Get the cookie value 90 | * 91 | * Attributes and other data can be accessed via methods. 92 | */ 93 | public function __toString() { 94 | return $this->value; 95 | } 96 | 97 | /** 98 | * Parse a cookie string into a cookie object 99 | * 100 | * Based on Mozilla's parsing code in Firefox and related projects, which 101 | * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 102 | * specifies some of this handling, but not in a thorough manner. 103 | * 104 | * @param string Cookie header value (from a Set-Cookie header) 105 | * @return Requests_Cookie Parsed cookie object 106 | */ 107 | public static function parse($string, $name = '') { 108 | $parts = explode(';', $string); 109 | $kvparts = array_shift($parts); 110 | 111 | if (!empty($name)) { 112 | $value = $string; 113 | } 114 | elseif (strpos($kvparts, '=') === false) { 115 | // Some sites might only have a value without the equals separator. 116 | // Deviate from RFC 6265 and pretend it was actually a blank name 117 | // (`=foo`) 118 | // 119 | // https://bugzilla.mozilla.org/show_bug.cgi?id=169091 120 | $name = ''; 121 | $value = $kvparts; 122 | } 123 | else { 124 | list($name, $value) = explode('=', $kvparts, 2); 125 | } 126 | $name = trim($name); 127 | $value = trim($value); 128 | 129 | // Attribute key are handled case-insensitively 130 | $attributes = new Requests_Utility_CaseInsensitiveDictionary(); 131 | 132 | if (!empty($parts)) { 133 | foreach ($parts as $part) { 134 | if (strpos($part, '=') === false) { 135 | $part_key = $part; 136 | $part_value = true; 137 | } 138 | else { 139 | list($part_key, $part_value) = explode('=', $part, 2); 140 | $part_value = trim($part_value); 141 | } 142 | 143 | $part_key = trim($part_key); 144 | $attributes[$part_key] = $part_value; 145 | } 146 | } 147 | 148 | return new Requests_Cookie($name, $value, $attributes); 149 | } 150 | 151 | /** 152 | * Parse all Set-Cookie headers from request headers 153 | * 154 | * @param Requests_Response_Headers $headers 155 | * @return array 156 | */ 157 | public static function parseFromHeaders(Requests_Response_Headers $headers) { 158 | $cookie_headers = $headers->getValues('Set-Cookie'); 159 | if (empty($cookie_headers)) { 160 | return array(); 161 | } 162 | 163 | $cookies = array(); 164 | foreach ($cookie_headers as $header) { 165 | $parsed = self::parse($header); 166 | $cookies[$parsed->name] = $parsed; 167 | } 168 | 169 | return $cookies; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /httpUtils/Requests/Cookie/Jar.php: -------------------------------------------------------------------------------- 1 | cookies = $cookies; 30 | } 31 | 32 | /** 33 | * Normalise cookie data into a Requests_Cookie 34 | * 35 | * @param string|Requests_Cookie $cookie 36 | * @return Requests_Cookie 37 | */ 38 | public function normalizeCookie($cookie, $key = null) { 39 | if ($cookie instanceof Requests_Cookie) { 40 | return $cookie; 41 | } 42 | 43 | return Requests_Cookie::parse($cookie, $key); 44 | } 45 | 46 | /** 47 | * Check if the given item exists 48 | * 49 | * @param string $key Item key 50 | * @return boolean Does the item exist? 51 | */ 52 | public function offsetExists($key) { 53 | return isset($this->cookies[$key]); 54 | } 55 | 56 | /** 57 | * Get the value for the item 58 | * 59 | * @param string $key Item key 60 | * @return string Item value 61 | */ 62 | public function offsetGet($key) { 63 | if (!isset($this->cookies[$key])) 64 | return null; 65 | 66 | return $this->cookies[$key]; 67 | } 68 | 69 | /** 70 | * Set the given item 71 | * 72 | * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`) 73 | * 74 | * @param string $key Item name 75 | * @param string $value Item value 76 | */ 77 | public function offsetSet($key, $value) { 78 | if ($key === null) { 79 | throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset'); 80 | } 81 | 82 | $this->cookies[$key] = $value; 83 | } 84 | 85 | /** 86 | * Unset the given header 87 | * 88 | * @param string $key 89 | */ 90 | public function offsetUnset($key) { 91 | unset($this->cookies[$key]); 92 | } 93 | 94 | /** 95 | * Get an iterator for the data 96 | * 97 | * @return ArrayIterator 98 | */ 99 | public function getIterator() { 100 | return new ArrayIterator($this->cookies); 101 | } 102 | 103 | /** 104 | * Register the cookie handler with the request's hooking system 105 | * 106 | * @param Requests_Hooker $hooks Hooking system 107 | */ 108 | public function register(Requests_Hooker $hooks) { 109 | $hooks->register('requests.before_request', array($this, 'before_request')); 110 | $hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check')); 111 | } 112 | 113 | /** 114 | * Add Cookie header to a request if we have any 115 | * 116 | * As per RFC 6265, cookies are separated by '; ' 117 | * 118 | * @param string $url 119 | * @param array $headers 120 | * @param array $data 121 | * @param string $type 122 | * @param array $options 123 | */ 124 | public function before_request(&$url, &$headers, &$data, &$type, &$options) { 125 | if (!empty($this->cookies)) { 126 | $cookies = array(); 127 | foreach ($this->cookies as $key => $cookie) { 128 | $cookie = $this->normalizeCookie($cookie, $key); 129 | $cookies[] = $cookie->formatForHeader(); 130 | } 131 | 132 | $headers['Cookie'] = implode('; ', $cookies); 133 | } 134 | } 135 | 136 | /** 137 | * Parse all cookies from a response and attach them to the response 138 | * 139 | * @var Requests_Response $response 140 | */ 141 | public function before_redirect_check(Requests_Response &$return) { 142 | $cookies = Requests_Cookie::parseFromHeaders($return->headers); 143 | $this->cookies = array_merge($this->cookies, $cookies); 144 | $return->cookies = $this; 145 | } 146 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Exception.php: -------------------------------------------------------------------------------- 1 | type = $type; 40 | $this->data = $data; 41 | } 42 | 43 | /** 44 | * Like {@see getCode()}, but a string code. 45 | * 46 | * @codeCoverageIgnore 47 | * @return string 48 | */ 49 | public function getType() { 50 | return $this->type; 51 | } 52 | 53 | /** 54 | * Gives any relevant data 55 | * 56 | * @codeCoverageIgnore 57 | * @return mixed 58 | */ 59 | public function getData() { 60 | return $this->data; 61 | } 62 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Exception/HTTP.php: -------------------------------------------------------------------------------- 1 | reason = $reason; 40 | } 41 | 42 | $message = sprintf('%d %s', $this->code, $this->reason); 43 | parent::__construct($message, 'httpresponse', $data, $this->code); 44 | } 45 | 46 | /** 47 | * Get the status message 48 | */ 49 | public function getReason() { 50 | return $this->reason; 51 | } 52 | 53 | /** 54 | * Get the correct exception class for a given error code 55 | * 56 | * @param int $code HTTP status code 57 | * @return string Exception class name to use 58 | */ 59 | public static function get_class($code) { 60 | $class = sprintf('Requests_Exception_HTTP_%d', $code); 61 | if (class_exists($class)) { 62 | return $class; 63 | } 64 | 65 | return 'Requests_Exception_HTTP_Unknown'; 66 | } 67 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Exception/HTTP/400.php: -------------------------------------------------------------------------------- 1 | code = $data->status_code; 40 | } 41 | 42 | parent::__construct($reason, $data); 43 | } 44 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Hooker.php: -------------------------------------------------------------------------------- 1 | 0 is executed later 22 | */ 23 | public function register($hook, $callback, $priority = 0); 24 | 25 | /** 26 | * Dispatch a message 27 | * 28 | * @param string $hook Hook name 29 | * @param array $parameters Parameters to pass to callbacks 30 | * @return boolean Successfulness 31 | */ 32 | public function dispatch($hook, $parameters = array()); 33 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Hooks.php: -------------------------------------------------------------------------------- 1 | 0 is executed later 29 | */ 30 | public function register($hook, $callback, $priority = 0) { 31 | if (!isset($this->hooks[$hook])) { 32 | $this->hooks[$hook] = array(); 33 | } 34 | if (!isset($this->hooks[$hook][$priority])) { 35 | $this->hooks[$hook][$priority] = array(); 36 | } 37 | 38 | $this->hooks[$hook][$priority][] = $callback; 39 | } 40 | 41 | /** 42 | * Dispatch a message 43 | * 44 | * @param string $hook Hook name 45 | * @param array $parameters Parameters to pass to callbacks 46 | * @return boolean Successfulness 47 | */ 48 | public function dispatch($hook, $parameters = array()) { 49 | if (empty($this->hooks[$hook])) { 50 | return false; 51 | } 52 | 53 | foreach ($this->hooks[$hook] as $priority => $hooked) { 54 | foreach ($hooked as $callback) { 55 | call_user_func_array($callback, $parameters); 56 | } 57 | } 58 | 59 | return true; 60 | } 61 | } -------------------------------------------------------------------------------- /httpUtils/Requests/IDNAEncoder.php: -------------------------------------------------------------------------------- 1 | 0) { 177 | if ($position + $length > $strlen) { 178 | throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); 179 | } 180 | for ($position++; $remaining > 0; $position++) { 181 | $value = ord($input[$position]); 182 | 183 | // If it is invalid, count the sequence as invalid and reprocess the current byte: 184 | if (($value & 0xC0) !== 0x80) { 185 | throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); 186 | } 187 | 188 | $character |= ($value & 0x3F) << (--$remaining * 6); 189 | } 190 | $position--; 191 | } 192 | 193 | if ( 194 | // Non-shortest form sequences are invalid 195 | $length > 1 && $character <= 0x7F 196 | || $length > 2 && $character <= 0x7FF 197 | || $length > 3 && $character <= 0xFFFF 198 | // Outside of range of ucschar codepoints 199 | // Noncharacters 200 | || ($character & 0xFFFE) === 0xFFFE 201 | || $character >= 0xFDD0 && $character <= 0xFDEF 202 | || ( 203 | // Everything else not in ucschar 204 | $character > 0xD7FF && $character < 0xF900 205 | || $character < 0x20 206 | || $character > 0x7E && $character < 0xA0 207 | || $character > 0xEFFFD 208 | ) 209 | ) { 210 | throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); 211 | } 212 | 213 | $codepoints[] = $character; 214 | } 215 | 216 | return $codepoints; 217 | } 218 | 219 | /** 220 | * RFC3492-compliant encoder 221 | * 222 | * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code 223 | * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) 224 | * 225 | * @param string $input UTF-8 encoded string to encode 226 | * @return string Punycode-encoded string 227 | */ 228 | public static function punycode_encode($input) { 229 | $output = ''; 230 | # let n = initial_n 231 | $n = self::BOOTSTRAP_INITIAL_N; 232 | # let delta = 0 233 | $delta = 0; 234 | # let bias = initial_bias 235 | $bias = self::BOOTSTRAP_INITIAL_BIAS; 236 | # let h = b = the number of basic code points in the input 237 | $h = $b = 0; // see loop 238 | # copy them to the output in order 239 | $codepoints = self::utf8_to_codepoints($input); 240 | 241 | foreach ($codepoints as $char) { 242 | if ($char < 128) { 243 | // Character is valid ASCII 244 | // TODO: this should also check if it's valid for a URL 245 | $output .= chr($char); 246 | $h++; 247 | } 248 | // Check if the character is non-ASCII, but below initial n 249 | // This never occurs for Punycode, so ignore in coverage 250 | // @codeCoverageIgnoreStart 251 | elseif ($char < $n) { 252 | throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char); 253 | } 254 | // @codeCoverageIgnoreEnd 255 | else { 256 | $extended[$char] = true; 257 | } 258 | } 259 | $extended = array_keys($extended); 260 | sort($extended); 261 | $b = $h; 262 | # [copy them] followed by a delimiter if b > 0 263 | if (strlen($output) > 0) { 264 | $output .= '-'; 265 | } 266 | # {if the input contains a non-basic code point < n then fail} 267 | # while h < length(input) do begin 268 | while ($h < count($codepoints)) { 269 | # let m = the minimum code point >= n in the input 270 | $m = array_shift($extended); 271 | //printf('next code point to insert is %s' . PHP_EOL, dechex($m)); 272 | # let delta = delta + (m - n) * (h + 1), fail on overflow 273 | $delta += ($m - $n) * ($h + 1); 274 | # let n = m 275 | $n = $m; 276 | # for each code point c in the input (in order) do begin 277 | for ($num = 0; $num < count($codepoints); $num++) { 278 | $c = $codepoints[$num]; 279 | # if c < n then increment delta, fail on overflow 280 | if ($c < $n) { 281 | $delta++; 282 | } 283 | # if c == n then begin 284 | elseif ($c === $n) { 285 | # let q = delta 286 | $q = $delta; 287 | # for k = base to infinity in steps of base do begin 288 | for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) { 289 | # let t = tmin if k <= bias {+ tmin}, or 290 | # tmax if k >= bias + tmax, or k - bias otherwise 291 | if ($k <= ($bias + self::BOOTSTRAP_TMIN)) { 292 | $t = self::BOOTSTRAP_TMIN; 293 | } 294 | elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) { 295 | $t = self::BOOTSTRAP_TMAX; 296 | } 297 | else { 298 | $t = $k - $bias; 299 | } 300 | # if q < t then break 301 | if ($q < $t) { 302 | break; 303 | } 304 | # output the code point for digit t + ((q - t) mod (base - t)) 305 | $digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t)); 306 | //printf('needed delta is %d, encodes as "%s"' . PHP_EOL, $delta, self::digit_to_char($digit)); 307 | $output .= self::digit_to_char($digit); 308 | # let q = (q - t) div (base - t) 309 | $q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t)); 310 | # end 311 | } 312 | # output the code point for digit q 313 | $output .= self::digit_to_char($q); 314 | //printf('needed delta is %d, encodes as "%s"' . PHP_EOL, $delta, self::digit_to_char($q)); 315 | # let bias = adapt(delta, h + 1, test h equals b?) 316 | $bias = self::adapt($delta, $h + 1, $h === $b); 317 | //printf('bias becomes %d' . PHP_EOL, $bias); 318 | # let delta = 0 319 | $delta = 0; 320 | # increment h 321 | $h++; 322 | # end 323 | } 324 | # end 325 | } 326 | # increment delta and n 327 | $delta++; 328 | $n++; 329 | # end 330 | } 331 | 332 | return $output; 333 | } 334 | 335 | /** 336 | * Convert a digit to its respective character 337 | * 338 | * @see http://tools.ietf.org/html/rfc3492#section-5 339 | * @throws Requests_Exception On invalid digit (`idna.invalid_digit`) 340 | * 341 | * @param int $digit Digit in the range 0-35 342 | * @return string Single character corresponding to digit 343 | */ 344 | protected static function digit_to_char($digit) { 345 | // @codeCoverageIgnoreStart 346 | // As far as I know, this never happens, but still good to be sure. 347 | if ($digit < 0 || $digit > 35) { 348 | throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); 349 | } 350 | // @codeCoverageIgnoreEnd 351 | $digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; 352 | return substr($digits, $digit, 1); 353 | } 354 | 355 | /** 356 | * Adapt the bias 357 | * 358 | * @see http://tools.ietf.org/html/rfc3492#section-6.1 359 | * @param int $delta 360 | * @param int $numpoints 361 | * @param bool $firsttime 362 | * @return int New bias 363 | */ 364 | protected static function adapt($delta, $numpoints, $firsttime) { 365 | # function adapt(delta,numpoints,firsttime): 366 | # if firsttime then let delta = delta div damp 367 | if ($firsttime) { 368 | $delta = floor($delta / self::BOOTSTRAP_DAMP); 369 | } 370 | # else let delta = delta div 2 371 | else { 372 | $delta = floor($delta / 2); 373 | } 374 | # let delta = delta + (delta div numpoints) 375 | $delta += floor($delta / $numpoints); 376 | # let k = 0 377 | $k = 0; 378 | # while delta > ((base - tmin) * tmax) div 2 do begin 379 | $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); 380 | while ($delta > $max) { 381 | # let delta = delta div (base - tmin) 382 | $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); 383 | # let k = k + base 384 | $k += self::BOOTSTRAP_BASE; 385 | # end 386 | } 387 | # return k + (((base - tmin + 1) * delta) div (delta + skew)) 388 | return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); 389 | } 390 | } -------------------------------------------------------------------------------- /httpUtils/Requests/IPv6.php: -------------------------------------------------------------------------------- 1 | FF01:0:0:0:0:0:0:101 28 | * ::1 -> 0:0:0:0:0:0:0:1 29 | * 30 | * @author Alexander Merz 31 | * @author elfrink at introweb dot nl 32 | * @author Josh Peck 33 | * @copyright 2003-2005 The PHP Group 34 | * @license http://www.opensource.org/licenses/bsd-license.php 35 | * @param string $ip An IPv6 address 36 | * @return string The uncompressed IPv6 address 37 | */ 38 | public static function uncompress($ip) 39 | { 40 | $c1 = -1; 41 | $c2 = -1; 42 | if (substr_count($ip, '::') === 1) 43 | { 44 | list($ip1, $ip2) = explode('::', $ip); 45 | if ($ip1 === '') 46 | { 47 | $c1 = -1; 48 | } 49 | else 50 | { 51 | $c1 = substr_count($ip1, ':'); 52 | } 53 | if ($ip2 === '') 54 | { 55 | $c2 = -1; 56 | } 57 | else 58 | { 59 | $c2 = substr_count($ip2, ':'); 60 | } 61 | if (strpos($ip2, '.') !== false) 62 | { 63 | $c2++; 64 | } 65 | // :: 66 | if ($c1 === -1 && $c2 === -1) 67 | { 68 | $ip = '0:0:0:0:0:0:0:0'; 69 | } 70 | // ::xxx 71 | else if ($c1 === -1) 72 | { 73 | $fill = str_repeat('0:', 7 - $c2); 74 | $ip = str_replace('::', $fill, $ip); 75 | } 76 | // xxx:: 77 | else if ($c2 === -1) 78 | { 79 | $fill = str_repeat(':0', 7 - $c1); 80 | $ip = str_replace('::', $fill, $ip); 81 | } 82 | // xxx::xxx 83 | else 84 | { 85 | $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); 86 | $ip = str_replace('::', $fill, $ip); 87 | } 88 | } 89 | return $ip; 90 | } 91 | 92 | /** 93 | * Compresses an IPv6 address 94 | * 95 | * RFC 4291 allows you to compress consecutive zero pieces in an address to 96 | * '::'. This method expects a valid IPv6 address and compresses consecutive 97 | * zero pieces to '::'. 98 | * 99 | * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 100 | * 0:0:0:0:0:0:0:1 -> ::1 101 | * 102 | * @see uncompress() 103 | * @param string $ip An IPv6 address 104 | * @return string The compressed IPv6 address 105 | */ 106 | public static function compress($ip) 107 | { 108 | // Prepare the IP to be compressed 109 | $ip = self::uncompress($ip); 110 | $ip_parts = self::split_v6_v4($ip); 111 | 112 | // Replace all leading zeros 113 | $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); 114 | 115 | // Find bunches of zeros 116 | if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) 117 | { 118 | $max = 0; 119 | $pos = null; 120 | foreach ($matches[0] as $match) 121 | { 122 | if (strlen($match[0]) > $max) 123 | { 124 | $max = strlen($match[0]); 125 | $pos = $match[1]; 126 | } 127 | } 128 | 129 | $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); 130 | } 131 | 132 | if ($ip_parts[1] !== '') 133 | { 134 | return implode(':', $ip_parts); 135 | } 136 | else 137 | { 138 | return $ip_parts[0]; 139 | } 140 | } 141 | 142 | /** 143 | * Splits an IPv6 address into the IPv6 and IPv4 representation parts 144 | * 145 | * RFC 4291 allows you to represent the last two parts of an IPv6 address 146 | * using the standard IPv4 representation 147 | * 148 | * Example: 0:0:0:0:0:0:13.1.68.3 149 | * 0:0:0:0:0:FFFF:129.144.52.38 150 | * 151 | * @param string $ip An IPv6 address 152 | * @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part 153 | */ 154 | private static function split_v6_v4($ip) 155 | { 156 | if (strpos($ip, '.') !== false) 157 | { 158 | $pos = strrpos($ip, ':'); 159 | $ipv6_part = substr($ip, 0, $pos); 160 | $ipv4_part = substr($ip, $pos + 1); 161 | return array($ipv6_part, $ipv4_part); 162 | } 163 | else 164 | { 165 | return array($ip, ''); 166 | } 167 | } 168 | 169 | /** 170 | * Checks an IPv6 address 171 | * 172 | * Checks if the given IP is a valid IPv6 address 173 | * 174 | * @param string $ip An IPv6 address 175 | * @return bool true if $ip is a valid IPv6 address 176 | */ 177 | public static function check_ipv6($ip) 178 | { 179 | $ip = self::uncompress($ip); 180 | list($ipv6, $ipv4) = self::split_v6_v4($ip); 181 | $ipv6 = explode(':', $ipv6); 182 | $ipv4 = explode('.', $ipv4); 183 | if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) 184 | { 185 | foreach ($ipv6 as $ipv6_part) 186 | { 187 | // The section can't be empty 188 | if ($ipv6_part === '') 189 | return false; 190 | 191 | // Nor can it be over four characters 192 | if (strlen($ipv6_part) > 4) 193 | return false; 194 | 195 | // Remove leading zeros (this is safe because of the above) 196 | $ipv6_part = ltrim($ipv6_part, '0'); 197 | if ($ipv6_part === '') 198 | $ipv6_part = '0'; 199 | 200 | // Check the value is valid 201 | $value = hexdec($ipv6_part); 202 | if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) 203 | return false; 204 | } 205 | if (count($ipv4) === 4) 206 | { 207 | foreach ($ipv4 as $ipv4_part) 208 | { 209 | $value = (int) $ipv4_part; 210 | if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) 211 | return false; 212 | } 213 | } 214 | return true; 215 | } 216 | else 217 | { 218 | return false; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /httpUtils/Requests/IRI.php: -------------------------------------------------------------------------------- 1 | array( 108 | 'port' => 674 109 | ), 110 | 'dict' => array( 111 | 'port' => 2628 112 | ), 113 | 'file' => array( 114 | 'ihost' => 'localhost' 115 | ), 116 | 'http' => array( 117 | 'port' => 80, 118 | 'ipath' => '/' 119 | ), 120 | 'https' => array( 121 | 'port' => 443, 122 | 'ipath' => '/' 123 | ), 124 | ); 125 | 126 | /** 127 | * Return the entire IRI when you try and read the object as a string 128 | * 129 | * @return string 130 | */ 131 | public function __toString() 132 | { 133 | return $this->get_iri(); 134 | } 135 | 136 | /** 137 | * Overload __set() to provide access via properties 138 | * 139 | * @param string $name Property name 140 | * @param mixed $value Property value 141 | */ 142 | public function __set($name, $value) 143 | { 144 | if (method_exists($this, 'set_' . $name)) 145 | { 146 | call_user_func(array($this, 'set_' . $name), $value); 147 | } 148 | elseif ( 149 | $name === 'iauthority' 150 | || $name === 'iuserinfo' 151 | || $name === 'ihost' 152 | || $name === 'ipath' 153 | || $name === 'iquery' 154 | || $name === 'ifragment' 155 | ) 156 | { 157 | call_user_func(array($this, 'set_' . substr($name, 1)), $value); 158 | } 159 | } 160 | 161 | /** 162 | * Overload __get() to provide access via properties 163 | * 164 | * @param string $name Property name 165 | * @return mixed 166 | */ 167 | public function __get($name) 168 | { 169 | // isset() returns false for null, we don't want to do that 170 | // Also why we use array_key_exists below instead of isset() 171 | $props = get_object_vars($this); 172 | 173 | if ( 174 | $name === 'iri' || 175 | $name === 'uri' || 176 | $name === 'iauthority' || 177 | $name === 'authority' 178 | ) 179 | { 180 | $return = $this->{"get_$name"}(); 181 | } 182 | elseif (array_key_exists($name, $props)) 183 | { 184 | $return = $this->$name; 185 | } 186 | // host -> ihost 187 | elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) 188 | { 189 | $name = $prop; 190 | $return = $this->$prop; 191 | } 192 | // ischeme -> scheme 193 | elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) 194 | { 195 | $name = $prop; 196 | $return = $this->$prop; 197 | } 198 | else 199 | { 200 | trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); 201 | $return = null; 202 | } 203 | 204 | if ($return === null && isset($this->normalization[$this->scheme][$name])) 205 | { 206 | return $this->normalization[$this->scheme][$name]; 207 | } 208 | else 209 | { 210 | return $return; 211 | } 212 | } 213 | 214 | /** 215 | * Overload __isset() to provide access via properties 216 | * 217 | * @param string $name Property name 218 | * @return bool 219 | */ 220 | public function __isset($name) 221 | { 222 | if (method_exists($this, 'get_' . $name) || isset($this->$name)) 223 | { 224 | return true; 225 | } 226 | else 227 | { 228 | return false; 229 | } 230 | } 231 | 232 | /** 233 | * Overload __unset() to provide access via properties 234 | * 235 | * @param string $name Property name 236 | */ 237 | public function __unset($name) 238 | { 239 | if (method_exists($this, 'set_' . $name)) 240 | { 241 | call_user_func(array($this, 'set_' . $name), ''); 242 | } 243 | } 244 | 245 | /** 246 | * Create a new IRI object, from a specified string 247 | * 248 | * @param string $iri 249 | */ 250 | public function __construct($iri = null) 251 | { 252 | $this->set_iri($iri); 253 | } 254 | 255 | /** 256 | * Create a new IRI object by resolving a relative IRI 257 | * 258 | * Returns false if $base is not absolute, otherwise an IRI. 259 | * 260 | * @param IRI|string $base (Absolute) Base IRI 261 | * @param IRI|string $relative Relative IRI 262 | * @return IRI|false 263 | */ 264 | public static function absolutize($base, $relative) 265 | { 266 | if (!($relative instanceof Requests_IRI)) 267 | { 268 | $relative = new Requests_IRI($relative); 269 | } 270 | if (!$relative->is_valid()) 271 | { 272 | return false; 273 | } 274 | elseif ($relative->scheme !== null) 275 | { 276 | return clone $relative; 277 | } 278 | else 279 | { 280 | if (!($base instanceof Requests_IRI)) 281 | { 282 | $base = new Requests_IRI($base); 283 | } 284 | if ($base->scheme !== null && $base->is_valid()) 285 | { 286 | if ($relative->get_iri() !== '') 287 | { 288 | if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) 289 | { 290 | $target = clone $relative; 291 | $target->scheme = $base->scheme; 292 | } 293 | else 294 | { 295 | $target = new Requests_IRI; 296 | $target->scheme = $base->scheme; 297 | $target->iuserinfo = $base->iuserinfo; 298 | $target->ihost = $base->ihost; 299 | $target->port = $base->port; 300 | if ($relative->ipath !== '') 301 | { 302 | if ($relative->ipath[0] === '/') 303 | { 304 | $target->ipath = $relative->ipath; 305 | } 306 | elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') 307 | { 308 | $target->ipath = '/' . $relative->ipath; 309 | } 310 | elseif (($last_segment = strrpos($base->ipath, '/')) !== false) 311 | { 312 | $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; 313 | } 314 | else 315 | { 316 | $target->ipath = $relative->ipath; 317 | } 318 | $target->ipath = $target->remove_dot_segments($target->ipath); 319 | $target->iquery = $relative->iquery; 320 | } 321 | else 322 | { 323 | $target->ipath = $base->ipath; 324 | if ($relative->iquery !== null) 325 | { 326 | $target->iquery = $relative->iquery; 327 | } 328 | elseif ($base->iquery !== null) 329 | { 330 | $target->iquery = $base->iquery; 331 | } 332 | } 333 | $target->ifragment = $relative->ifragment; 334 | } 335 | } 336 | else 337 | { 338 | $target = clone $base; 339 | $target->ifragment = null; 340 | } 341 | $target->scheme_normalization(); 342 | return $target; 343 | } 344 | else 345 | { 346 | return false; 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * Parse an IRI into scheme/authority/path/query/fragment segments 353 | * 354 | * @param string $iri 355 | * @return array 356 | */ 357 | protected function parse_iri($iri) 358 | { 359 | $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); 360 | if (preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match)) 361 | { 362 | if ($match[1] === '') 363 | { 364 | $match['scheme'] = null; 365 | } 366 | if (!isset($match[3]) || $match[3] === '') 367 | { 368 | $match['authority'] = null; 369 | } 370 | if (!isset($match[5])) 371 | { 372 | $match['path'] = ''; 373 | } 374 | if (!isset($match[6]) || $match[6] === '') 375 | { 376 | $match['query'] = null; 377 | } 378 | if (!isset($match[8]) || $match[8] === '') 379 | { 380 | $match['fragment'] = null; 381 | } 382 | return $match; 383 | } 384 | else 385 | { 386 | trigger_error('This should never happen', E_USER_ERROR); 387 | die; 388 | } 389 | } 390 | 391 | /** 392 | * Remove dot segments from a path 393 | * 394 | * @param string $input 395 | * @return string 396 | */ 397 | protected function remove_dot_segments($input) 398 | { 399 | $output = ''; 400 | while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') 401 | { 402 | // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, 403 | if (strpos($input, '../') === 0) 404 | { 405 | $input = substr($input, 3); 406 | } 407 | elseif (strpos($input, './') === 0) 408 | { 409 | $input = substr($input, 2); 410 | } 411 | // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, 412 | elseif (strpos($input, '/./') === 0) 413 | { 414 | $input = substr($input, 2); 415 | } 416 | elseif ($input === '/.') 417 | { 418 | $input = '/'; 419 | } 420 | // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, 421 | elseif (strpos($input, '/../') === 0) 422 | { 423 | $input = substr($input, 3); 424 | $output = substr_replace($output, '', strrpos($output, '/')); 425 | } 426 | elseif ($input === '/..') 427 | { 428 | $input = '/'; 429 | $output = substr_replace($output, '', strrpos($output, '/')); 430 | } 431 | // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, 432 | elseif ($input === '.' || $input === '..') 433 | { 434 | $input = ''; 435 | } 436 | // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer 437 | elseif (($pos = strpos($input, '/', 1)) !== false) 438 | { 439 | $output .= substr($input, 0, $pos); 440 | $input = substr_replace($input, '', 0, $pos); 441 | } 442 | else 443 | { 444 | $output .= $input; 445 | $input = ''; 446 | } 447 | } 448 | return $output . $input; 449 | } 450 | 451 | /** 452 | * Replace invalid character with percent encoding 453 | * 454 | * @param string $string Input string 455 | * @param string $extra_chars Valid characters not in iunreserved or 456 | * iprivate (this is ASCII-only) 457 | * @param bool $iprivate Allow iprivate 458 | * @return string 459 | */ 460 | protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) 461 | { 462 | // Normalize as many pct-encoded sections as possible 463 | $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array(&$this, 'remove_iunreserved_percent_encoded'), $string); 464 | 465 | // Replace invalid percent characters 466 | $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); 467 | 468 | // Add unreserved and % to $extra_chars (the latter is safe because all 469 | // pct-encoded sections are now valid). 470 | $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; 471 | 472 | // Now replace any bytes that aren't allowed with their pct-encoded versions 473 | $position = 0; 474 | $strlen = strlen($string); 475 | while (($position += strspn($string, $extra_chars, $position)) < $strlen) 476 | { 477 | $value = ord($string[$position]); 478 | 479 | // Start position 480 | $start = $position; 481 | 482 | // By default we are valid 483 | $valid = true; 484 | 485 | // No one byte sequences are valid due to the while. 486 | // Two byte sequence: 487 | if (($value & 0xE0) === 0xC0) 488 | { 489 | $character = ($value & 0x1F) << 6; 490 | $length = 2; 491 | $remaining = 1; 492 | } 493 | // Three byte sequence: 494 | elseif (($value & 0xF0) === 0xE0) 495 | { 496 | $character = ($value & 0x0F) << 12; 497 | $length = 3; 498 | $remaining = 2; 499 | } 500 | // Four byte sequence: 501 | elseif (($value & 0xF8) === 0xF0) 502 | { 503 | $character = ($value & 0x07) << 18; 504 | $length = 4; 505 | $remaining = 3; 506 | } 507 | // Invalid byte: 508 | else 509 | { 510 | $valid = false; 511 | $length = 1; 512 | $remaining = 0; 513 | } 514 | 515 | if ($remaining) 516 | { 517 | if ($position + $length <= $strlen) 518 | { 519 | for ($position++; $remaining; $position++) 520 | { 521 | $value = ord($string[$position]); 522 | 523 | // Check that the byte is valid, then add it to the character: 524 | if (($value & 0xC0) === 0x80) 525 | { 526 | $character |= ($value & 0x3F) << (--$remaining * 6); 527 | } 528 | // If it is invalid, count the sequence as invalid and reprocess the current byte: 529 | else 530 | { 531 | $valid = false; 532 | $position--; 533 | break; 534 | } 535 | } 536 | } 537 | else 538 | { 539 | $position = $strlen - 1; 540 | $valid = false; 541 | } 542 | } 543 | 544 | // Percent encode anything invalid or not in ucschar 545 | if ( 546 | // Invalid sequences 547 | !$valid 548 | // Non-shortest form sequences are invalid 549 | || $length > 1 && $character <= 0x7F 550 | || $length > 2 && $character <= 0x7FF 551 | || $length > 3 && $character <= 0xFFFF 552 | // Outside of range of ucschar codepoints 553 | // Noncharacters 554 | || ($character & 0xFFFE) === 0xFFFE 555 | || $character >= 0xFDD0 && $character <= 0xFDEF 556 | || ( 557 | // Everything else not in ucschar 558 | $character > 0xD7FF && $character < 0xF900 559 | || $character < 0xA0 560 | || $character > 0xEFFFD 561 | ) 562 | && ( 563 | // Everything not in iprivate, if it applies 564 | !$iprivate 565 | || $character < 0xE000 566 | || $character > 0x10FFFD 567 | ) 568 | ) 569 | { 570 | // If we were a character, pretend we weren't, but rather an error. 571 | if ($valid) 572 | $position--; 573 | 574 | for ($j = $start; $j <= $position; $j++) 575 | { 576 | $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); 577 | $j += 2; 578 | $position += 2; 579 | $strlen += 2; 580 | } 581 | } 582 | } 583 | 584 | return $string; 585 | } 586 | 587 | /** 588 | * Callback function for preg_replace_callback. 589 | * 590 | * Removes sequences of percent encoded bytes that represent UTF-8 591 | * encoded characters in iunreserved 592 | * 593 | * @param array $match PCRE match 594 | * @return string Replacement 595 | */ 596 | protected function remove_iunreserved_percent_encoded($match) 597 | { 598 | // As we just have valid percent encoded sequences we can just explode 599 | // and ignore the first member of the returned array (an empty string). 600 | $bytes = explode('%', $match[0]); 601 | 602 | // Initialize the new string (this is what will be returned) and that 603 | // there are no bytes remaining in the current sequence (unsurprising 604 | // at the first byte!). 605 | $string = ''; 606 | $remaining = 0; 607 | 608 | // Loop over each and every byte, and set $value to its value 609 | for ($i = 1, $len = count($bytes); $i < $len; $i++) 610 | { 611 | $value = hexdec($bytes[$i]); 612 | 613 | // If we're the first byte of sequence: 614 | if (!$remaining) 615 | { 616 | // Start position 617 | $start = $i; 618 | 619 | // By default we are valid 620 | $valid = true; 621 | 622 | // One byte sequence: 623 | if ($value <= 0x7F) 624 | { 625 | $character = $value; 626 | $length = 1; 627 | } 628 | // Two byte sequence: 629 | elseif (($value & 0xE0) === 0xC0) 630 | { 631 | $character = ($value & 0x1F) << 6; 632 | $length = 2; 633 | $remaining = 1; 634 | } 635 | // Three byte sequence: 636 | elseif (($value & 0xF0) === 0xE0) 637 | { 638 | $character = ($value & 0x0F) << 12; 639 | $length = 3; 640 | $remaining = 2; 641 | } 642 | // Four byte sequence: 643 | elseif (($value & 0xF8) === 0xF0) 644 | { 645 | $character = ($value & 0x07) << 18; 646 | $length = 4; 647 | $remaining = 3; 648 | } 649 | // Invalid byte: 650 | else 651 | { 652 | $valid = false; 653 | $remaining = 0; 654 | } 655 | } 656 | // Continuation byte: 657 | else 658 | { 659 | // Check that the byte is valid, then add it to the character: 660 | if (($value & 0xC0) === 0x80) 661 | { 662 | $remaining--; 663 | $character |= ($value & 0x3F) << ($remaining * 6); 664 | } 665 | // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: 666 | else 667 | { 668 | $valid = false; 669 | $remaining = 0; 670 | $i--; 671 | } 672 | } 673 | 674 | // If we've reached the end of the current byte sequence, append it to Unicode::$data 675 | if (!$remaining) 676 | { 677 | // Percent encode anything invalid or not in iunreserved 678 | if ( 679 | // Invalid sequences 680 | !$valid 681 | // Non-shortest form sequences are invalid 682 | || $length > 1 && $character <= 0x7F 683 | || $length > 2 && $character <= 0x7FF 684 | || $length > 3 && $character <= 0xFFFF 685 | // Outside of range of iunreserved codepoints 686 | || $character < 0x2D 687 | || $character > 0xEFFFD 688 | // Noncharacters 689 | || ($character & 0xFFFE) === 0xFFFE 690 | || $character >= 0xFDD0 && $character <= 0xFDEF 691 | // Everything else not in iunreserved (this is all BMP) 692 | || $character === 0x2F 693 | || $character > 0x39 && $character < 0x41 694 | || $character > 0x5A && $character < 0x61 695 | || $character > 0x7A && $character < 0x7E 696 | || $character > 0x7E && $character < 0xA0 697 | || $character > 0xD7FF && $character < 0xF900 698 | ) 699 | { 700 | for ($j = $start; $j <= $i; $j++) 701 | { 702 | $string .= '%' . strtoupper($bytes[$j]); 703 | } 704 | } 705 | else 706 | { 707 | for ($j = $start; $j <= $i; $j++) 708 | { 709 | $string .= chr(hexdec($bytes[$j])); 710 | } 711 | } 712 | } 713 | } 714 | 715 | // If we have any bytes left over they are invalid (i.e., we are 716 | // mid-way through a multi-byte sequence) 717 | if ($remaining) 718 | { 719 | for ($j = $start; $j < $len; $j++) 720 | { 721 | $string .= '%' . strtoupper($bytes[$j]); 722 | } 723 | } 724 | 725 | return $string; 726 | } 727 | 728 | protected function scheme_normalization() 729 | { 730 | if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) 731 | { 732 | $this->iuserinfo = null; 733 | } 734 | if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) 735 | { 736 | $this->ihost = null; 737 | } 738 | if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) 739 | { 740 | $this->port = null; 741 | } 742 | if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) 743 | { 744 | $this->ipath = ''; 745 | } 746 | if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) 747 | { 748 | $this->iquery = null; 749 | } 750 | if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) 751 | { 752 | $this->ifragment = null; 753 | } 754 | } 755 | 756 | /** 757 | * Check if the object represents a valid IRI. This needs to be done on each 758 | * call as some things change depending on another part of the IRI. 759 | * 760 | * @return bool 761 | */ 762 | public function is_valid() 763 | { 764 | $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; 765 | if ($this->ipath !== '' && 766 | ( 767 | $isauthority && ( 768 | $this->ipath[0] !== '/' || 769 | substr($this->ipath, 0, 2) === '//' 770 | ) || 771 | ( 772 | $this->scheme === null && 773 | !$isauthority && 774 | strpos($this->ipath, ':') !== false && 775 | (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) 776 | ) 777 | ) 778 | ) 779 | { 780 | return false; 781 | } 782 | 783 | return true; 784 | } 785 | 786 | /** 787 | * Set the entire IRI. Returns true on success, false on failure (if there 788 | * are any invalid characters). 789 | * 790 | * @param string $iri 791 | * @return bool 792 | */ 793 | protected function set_iri($iri) 794 | { 795 | static $cache; 796 | if (!$cache) 797 | { 798 | $cache = array(); 799 | } 800 | 801 | if ($iri === null) 802 | { 803 | return true; 804 | } 805 | elseif (isset($cache[$iri])) 806 | { 807 | list($this->scheme, 808 | $this->iuserinfo, 809 | $this->ihost, 810 | $this->port, 811 | $this->ipath, 812 | $this->iquery, 813 | $this->ifragment, 814 | $return) = $cache[$iri]; 815 | return $return; 816 | } 817 | else 818 | { 819 | $parsed = $this->parse_iri((string) $iri); 820 | 821 | $return = $this->set_scheme($parsed['scheme']) 822 | && $this->set_authority($parsed['authority']) 823 | && $this->set_path($parsed['path']) 824 | && $this->set_query($parsed['query']) 825 | && $this->set_fragment($parsed['fragment']); 826 | 827 | $cache[$iri] = array($this->scheme, 828 | $this->iuserinfo, 829 | $this->ihost, 830 | $this->port, 831 | $this->ipath, 832 | $this->iquery, 833 | $this->ifragment, 834 | $return); 835 | return $return; 836 | } 837 | } 838 | 839 | /** 840 | * Set the scheme. Returns true on success, false on failure (if there are 841 | * any invalid characters). 842 | * 843 | * @param string $scheme 844 | * @return bool 845 | */ 846 | protected function set_scheme($scheme) 847 | { 848 | if ($scheme === null) 849 | { 850 | $this->scheme = null; 851 | } 852 | elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) 853 | { 854 | $this->scheme = null; 855 | return false; 856 | } 857 | else 858 | { 859 | $this->scheme = strtolower($scheme); 860 | } 861 | return true; 862 | } 863 | 864 | /** 865 | * Set the authority. Returns true on success, false on failure (if there are 866 | * any invalid characters). 867 | * 868 | * @param string $authority 869 | * @return bool 870 | */ 871 | protected function set_authority($authority) 872 | { 873 | static $cache; 874 | if (!$cache) 875 | $cache = array(); 876 | 877 | if ($authority === null) 878 | { 879 | $this->iuserinfo = null; 880 | $this->ihost = null; 881 | $this->port = null; 882 | return true; 883 | } 884 | elseif (isset($cache[$authority])) 885 | { 886 | list($this->iuserinfo, 887 | $this->ihost, 888 | $this->port, 889 | $return) = $cache[$authority]; 890 | 891 | return $return; 892 | } 893 | else 894 | { 895 | $remaining = $authority; 896 | if (($iuserinfo_end = strrpos($remaining, '@')) !== false) 897 | { 898 | $iuserinfo = substr($remaining, 0, $iuserinfo_end); 899 | $remaining = substr($remaining, $iuserinfo_end + 1); 900 | } 901 | else 902 | { 903 | $iuserinfo = null; 904 | } 905 | if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) 906 | { 907 | if (($port = substr($remaining, $port_start + 1)) === false) 908 | { 909 | $port = null; 910 | } 911 | $remaining = substr($remaining, 0, $port_start); 912 | } 913 | else 914 | { 915 | $port = null; 916 | } 917 | 918 | $return = $this->set_userinfo($iuserinfo) && 919 | $this->set_host($remaining) && 920 | $this->set_port($port); 921 | 922 | $cache[$authority] = array($this->iuserinfo, 923 | $this->ihost, 924 | $this->port, 925 | $return); 926 | 927 | return $return; 928 | } 929 | } 930 | 931 | /** 932 | * Set the iuserinfo. 933 | * 934 | * @param string $iuserinfo 935 | * @return bool 936 | */ 937 | protected function set_userinfo($iuserinfo) 938 | { 939 | if ($iuserinfo === null) 940 | { 941 | $this->iuserinfo = null; 942 | } 943 | else 944 | { 945 | $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); 946 | $this->scheme_normalization(); 947 | } 948 | 949 | return true; 950 | } 951 | 952 | /** 953 | * Set the ihost. Returns true on success, false on failure (if there are 954 | * any invalid characters). 955 | * 956 | * @param string $ihost 957 | * @return bool 958 | */ 959 | protected function set_host($ihost) 960 | { 961 | if ($ihost === null) 962 | { 963 | $this->ihost = null; 964 | return true; 965 | } 966 | elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') 967 | { 968 | if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) 969 | { 970 | $this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']'; 971 | } 972 | else 973 | { 974 | $this->ihost = null; 975 | return false; 976 | } 977 | } 978 | else 979 | { 980 | $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); 981 | 982 | // Lowercase, but ignore pct-encoded sections (as they should 983 | // remain uppercase). This must be done after the previous step 984 | // as that can add unescaped characters. 985 | $position = 0; 986 | $strlen = strlen($ihost); 987 | while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) 988 | { 989 | if ($ihost[$position] === '%') 990 | { 991 | $position += 3; 992 | } 993 | else 994 | { 995 | $ihost[$position] = strtolower($ihost[$position]); 996 | $position++; 997 | } 998 | } 999 | 1000 | $this->ihost = $ihost; 1001 | } 1002 | 1003 | $this->scheme_normalization(); 1004 | 1005 | return true; 1006 | } 1007 | 1008 | /** 1009 | * Set the port. Returns true on success, false on failure (if there are 1010 | * any invalid characters). 1011 | * 1012 | * @param string $port 1013 | * @return bool 1014 | */ 1015 | protected function set_port($port) 1016 | { 1017 | if ($port === null) 1018 | { 1019 | $this->port = null; 1020 | return true; 1021 | } 1022 | elseif (strspn($port, '0123456789') === strlen($port)) 1023 | { 1024 | $this->port = (int) $port; 1025 | $this->scheme_normalization(); 1026 | return true; 1027 | } 1028 | else 1029 | { 1030 | $this->port = null; 1031 | return false; 1032 | } 1033 | } 1034 | 1035 | /** 1036 | * Set the ipath. 1037 | * 1038 | * @param string $ipath 1039 | * @return bool 1040 | */ 1041 | protected function set_path($ipath) 1042 | { 1043 | static $cache; 1044 | if (!$cache) 1045 | { 1046 | $cache = array(); 1047 | } 1048 | 1049 | $ipath = (string) $ipath; 1050 | 1051 | if (isset($cache[$ipath])) 1052 | { 1053 | $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; 1054 | } 1055 | else 1056 | { 1057 | $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); 1058 | $removed = $this->remove_dot_segments($valid); 1059 | 1060 | $cache[$ipath] = array($valid, $removed); 1061 | $this->ipath = ($this->scheme !== null) ? $removed : $valid; 1062 | } 1063 | 1064 | $this->scheme_normalization(); 1065 | return true; 1066 | } 1067 | 1068 | /** 1069 | * Set the iquery. 1070 | * 1071 | * @param string $iquery 1072 | * @return bool 1073 | */ 1074 | protected function set_query($iquery) 1075 | { 1076 | if ($iquery === null) 1077 | { 1078 | $this->iquery = null; 1079 | } 1080 | else 1081 | { 1082 | $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); 1083 | $this->scheme_normalization(); 1084 | } 1085 | return true; 1086 | } 1087 | 1088 | /** 1089 | * Set the ifragment. 1090 | * 1091 | * @param string $ifragment 1092 | * @return bool 1093 | */ 1094 | protected function set_fragment($ifragment) 1095 | { 1096 | if ($ifragment === null) 1097 | { 1098 | $this->ifragment = null; 1099 | } 1100 | else 1101 | { 1102 | $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); 1103 | $this->scheme_normalization(); 1104 | } 1105 | return true; 1106 | } 1107 | 1108 | /** 1109 | * Convert an IRI to a URI (or parts thereof) 1110 | * 1111 | * @return string 1112 | */ 1113 | protected function to_uri($string) 1114 | { 1115 | static $non_ascii; 1116 | if (!$non_ascii) 1117 | { 1118 | $non_ascii = implode('', range("\x80", "\xFF")); 1119 | } 1120 | 1121 | $position = 0; 1122 | $strlen = strlen($string); 1123 | while (($position += strcspn($string, $non_ascii, $position)) < $strlen) 1124 | { 1125 | $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); 1126 | $position += 3; 1127 | $strlen += 2; 1128 | } 1129 | 1130 | return $string; 1131 | } 1132 | 1133 | /** 1134 | * Get the complete IRI 1135 | * 1136 | * @return string 1137 | */ 1138 | protected function get_iri() 1139 | { 1140 | if (!$this->is_valid()) 1141 | { 1142 | return false; 1143 | } 1144 | 1145 | $iri = ''; 1146 | if ($this->scheme !== null) 1147 | { 1148 | $iri .= $this->scheme . ':'; 1149 | } 1150 | if (($iauthority = $this->get_iauthority()) !== null) 1151 | { 1152 | $iri .= '//' . $iauthority; 1153 | } 1154 | $iri .= $this->ipath; 1155 | if ($this->iquery !== null) 1156 | { 1157 | $iri .= '?' . $this->iquery; 1158 | } 1159 | if ($this->ifragment !== null) 1160 | { 1161 | $iri .= '#' . $this->ifragment; 1162 | } 1163 | 1164 | return $iri; 1165 | } 1166 | 1167 | /** 1168 | * Get the complete URI 1169 | * 1170 | * @return string 1171 | */ 1172 | protected function get_uri() 1173 | { 1174 | return $this->to_uri($this->get_iri()); 1175 | } 1176 | 1177 | /** 1178 | * Get the complete iauthority 1179 | * 1180 | * @return string 1181 | */ 1182 | protected function get_iauthority() 1183 | { 1184 | if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) 1185 | { 1186 | $iauthority = ''; 1187 | if ($this->iuserinfo !== null) 1188 | { 1189 | $iauthority .= $this->iuserinfo . '@'; 1190 | } 1191 | if ($this->ihost !== null) 1192 | { 1193 | $iauthority .= $this->ihost; 1194 | } 1195 | if ($this->port !== null) 1196 | { 1197 | $iauthority .= ':' . $this->port; 1198 | } 1199 | return $iauthority; 1200 | } 1201 | else 1202 | { 1203 | return null; 1204 | } 1205 | } 1206 | 1207 | /** 1208 | * Get the complete authority 1209 | * 1210 | * @return string 1211 | */ 1212 | protected function get_authority() 1213 | { 1214 | $iauthority = $this->get_iauthority(); 1215 | if (is_string($iauthority)) 1216 | return $this->to_uri($iauthority); 1217 | else 1218 | return $iauthority; 1219 | } 1220 | } 1221 | -------------------------------------------------------------------------------- /httpUtils/Requests/Proxy.php: -------------------------------------------------------------------------------- 1 | proxy = $args; 60 | } 61 | elseif (is_array($args)) { 62 | if (count($args) == 1) { 63 | list($this->proxy) = $args; 64 | } 65 | elseif (count($args) == 3) { 66 | list($this->proxy, $this->user, $this->pass) = $args; 67 | $this->use_authentication = true; 68 | } 69 | else { 70 | throw new Requests_Exception( 'Invalid number of arguments', 'proxyhttpbadargs'); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Register the necessary callbacks 77 | * 78 | * @since 1.6 79 | * @see curl_before_send 80 | * @see fsockopen_remote_socket 81 | * @see fsockopen_remote_host_path 82 | * @see fsockopen_header 83 | * @param Requests_Hooks $hooks Hook system 84 | */ 85 | public function register(Requests_Hooks &$hooks) { 86 | $hooks->register('curl.before_send', array(&$this, 'curl_before_send')); 87 | 88 | $hooks->register('fsockopen.remote_socket', array(&$this, 'fsockopen_remote_socket')); 89 | $hooks->register('fsockopen.remote_host_path', array(&$this, 'fsockopen_remote_host_path')); 90 | if( $this->use_authentication ) { 91 | $hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header')); 92 | } 93 | } 94 | 95 | /** 96 | * Set cURL parameters before the data is sent 97 | * 98 | * @since 1.6 99 | * @param resource $handle cURL resource 100 | */ 101 | public function curl_before_send(&$handle) { 102 | curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); 103 | curl_setopt($handle, CURLOPT_PROXY, $this->proxy); 104 | 105 | if ($this->use_authentication) { 106 | curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); 107 | curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string()); 108 | } 109 | } 110 | 111 | /** 112 | * Alter remote socket information before opening socket connection 113 | * 114 | * @since 1.6 115 | * @param string $out HTTP header string 116 | */ 117 | public function fsockopen_remote_socket( &$remote_socket ) { 118 | $remote_socket = $this->proxy; 119 | } 120 | 121 | /** 122 | * Alter remote path before getting stream data 123 | * 124 | * @since 1.6 125 | * @param string $out HTTP header string 126 | */ 127 | public function fsockopen_remote_host_path( &$path, $url ) { 128 | $path = $url; 129 | } 130 | 131 | /** 132 | * Add extra headers to the request before sending 133 | * 134 | * @since 1.6 135 | * @param string $out HTTP header string 136 | */ 137 | public function fsockopen_header( &$out ) { 138 | $out .= "Proxy-Authorization: Basic " . base64_encode($this->get_auth_string()) . "\r\n"; 139 | } 140 | 141 | /** 142 | * Get the authentication string (user:pass) 143 | * 144 | * @since 1.6 145 | * @return string 146 | */ 147 | public function get_auth_string() { 148 | return $this->user . ':' . $this->pass; 149 | } 150 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Response.php: -------------------------------------------------------------------------------- 1 | headers = new Requests_Response_Headers(); 21 | } 22 | 23 | /** 24 | * Response body 25 | * @var string 26 | */ 27 | public $body = ''; 28 | 29 | /** 30 | * Raw HTTP data from the transport 31 | * @var string 32 | */ 33 | public $raw = ''; 34 | 35 | /** 36 | * Headers, as an associative array 37 | * @var array 38 | */ 39 | public $headers = array(); 40 | 41 | /** 42 | * Status code, false if non-blocking 43 | * @var integer|boolean 44 | */ 45 | public $status_code = false; 46 | 47 | /** 48 | * Whether the request succeeded or not 49 | * @var boolean 50 | */ 51 | public $success = false; 52 | 53 | /** 54 | * Number of redirects the request used 55 | * @var integer 56 | */ 57 | public $redirects = 0; 58 | 59 | /** 60 | * URL requested 61 | * @var string 62 | */ 63 | public $url = ''; 64 | 65 | /** 66 | * Previous requests (from redirects) 67 | * @var array Array of Requests_Response objects 68 | */ 69 | public $history = array(); 70 | 71 | /** 72 | * Cookies from the request 73 | */ 74 | public $cookies = array(); 75 | 76 | /** 77 | * Throws an exception if the request was not successful 78 | * 79 | * @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`) 80 | * @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404}) 81 | * @param boolean $allow_redirects Set to false to throw on a 3xx as well 82 | */ 83 | public function throw_for_status($allow_redirects = true) { 84 | if ($this->status_code >= 300 && $this->status_code < 400) { 85 | if (!$allow_redirects) { 86 | throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this); 87 | } 88 | } 89 | 90 | elseif (!$this->success) { 91 | $exception = Requests_Exception_HTTP::get_class($this->status_code); 92 | throw new $exception(null, $this); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Response/Headers.php: -------------------------------------------------------------------------------- 1 | data[$key])) 29 | return null; 30 | 31 | return $this->flatten($this->data[$key]); 32 | } 33 | 34 | /** 35 | * Set the given item 36 | * 37 | * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`) 38 | * 39 | * @param string $key Item name 40 | * @param string $value Item value 41 | */ 42 | public function offsetSet($key, $value) { 43 | if ($key === null) { 44 | throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset'); 45 | } 46 | 47 | $key = strtolower($key); 48 | 49 | if (!isset($this->data[$key])) { 50 | $this->data[$key] = array(); 51 | } 52 | 53 | $this->data[$key][] = $value; 54 | } 55 | 56 | /** 57 | * Get all values for a given header 58 | * 59 | * @param string $key 60 | * @return array Header values 61 | */ 62 | public function getValues($key) { 63 | $key = strtolower($key); 64 | if (!isset($this->data[$key])) 65 | return null; 66 | 67 | return $this->data[$key]; 68 | } 69 | 70 | /** 71 | * Flattens a value into a string 72 | * 73 | * Converts an array into a string by imploding values with a comma, as per 74 | * RFC2616's rules for folding headers. 75 | * 76 | * @param string|array $value Value to flatten 77 | * @return string Flattened value 78 | */ 79 | public function flatten($value) { 80 | if (is_array($value)) 81 | $value = implode(',', $value); 82 | 83 | return $value; 84 | } 85 | 86 | /** 87 | * Get an iterator for the data 88 | * 89 | * Converts the internal 90 | * @return ArrayIterator 91 | */ 92 | public function getIterator() { 93 | return new Requests_Utility_FilteredIterator($this->data, array($this, 'flatten')); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /httpUtils/Requests/SSL.php: -------------------------------------------------------------------------------- 1 | useragent = 'X';` 53 | * 54 | * @var array 55 | */ 56 | public $options = array(); 57 | 58 | /** 59 | * Create a new session 60 | * 61 | * @param string|null $url Base URL for requests 62 | * @param array $headers Default headers for requests 63 | * @param array $data Default data for requests 64 | * @param array $options Default options for requests 65 | */ 66 | public function __construct($url = null, $headers = array(), $data = array(), $options = array()) { 67 | $this->url = $url; 68 | $this->headers = $headers; 69 | $this->data = $data; 70 | $this->options = $options; 71 | 72 | if (empty($this->options['cookies'])) { 73 | $this->options['cookies'] = new Requests_Cookie_Jar(); 74 | } 75 | } 76 | 77 | /** 78 | * Get a property's value 79 | * 80 | * @param string $key Property key 81 | * @return mixed|null Property value, null if none found 82 | */ 83 | public function __get($key) { 84 | if (isset($this->options[$key])) 85 | return $this->options[$key]; 86 | 87 | return null; 88 | } 89 | 90 | /** 91 | * Set a property's value 92 | * 93 | * @param string $key Property key 94 | * @param mixed $value Property value 95 | */ 96 | public function __set($key, $value) { 97 | $this->options[$key] = $value; 98 | } 99 | 100 | /** 101 | * Remove a property's value 102 | * 103 | * @param string $key Property key 104 | */ 105 | public function __isset($key) { 106 | return isset($this->options[$key]); 107 | } 108 | 109 | /** 110 | * Remove a property's value 111 | * 112 | * @param string $key Property key 113 | */ 114 | public function __unset($key) { 115 | $this->options[$key] = null; 116 | } 117 | 118 | /**#@+ 119 | * @see request() 120 | * @param string $url 121 | * @param array $headers 122 | * @param array $options 123 | * @return Requests_Response 124 | */ 125 | /** 126 | * Send a GET request 127 | */ 128 | public function get($url, $headers = array(), $options = array()) { 129 | return $this->request($url, $headers, null, Requests::GET, $options); 130 | } 131 | 132 | /** 133 | * Send a HEAD request 134 | */ 135 | public function head($url, $headers = array(), $options = array()) { 136 | return $this->request($url, $headers, null, Requests::HEAD, $options); 137 | } 138 | 139 | /** 140 | * Send a DELETE request 141 | */ 142 | public function delete($url, $headers = array(), $options = array()) { 143 | return $this->request($url, $headers, null, Requests::DELETE, $options); 144 | } 145 | /**#@-*/ 146 | 147 | /**#@+ 148 | * @see request() 149 | * @param string $url 150 | * @param array $headers 151 | * @param array $data 152 | * @param array $options 153 | * @return Requests_Response 154 | */ 155 | /** 156 | * Send a POST request 157 | */ 158 | public function post($url, $headers = array(), $data = array(), $options = array()) { 159 | return $this->request($url, $headers, $data, Requests::POST, $options); 160 | } 161 | 162 | /** 163 | * Send a PUT request 164 | */ 165 | public function put($url, $headers = array(), $data = array(), $options = array()) { 166 | return $this->request($url, $headers, $data, Requests::PUT, $options); 167 | } 168 | 169 | /** 170 | * Send a PATCH request 171 | * 172 | * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the 173 | * specification recommends that should send an ETag 174 | * 175 | * @link http://tools.ietf.org/html/rfc5789 176 | */ 177 | public function patch($url, $headers, $data = array(), $options = array()) { 178 | return $this->request($url, $headers, $data, Requests::PATCH, $options); 179 | } 180 | /**#@-*/ 181 | 182 | /** 183 | * Main interface for HTTP requests 184 | * 185 | * This method initiates a request and sends it via a transport before 186 | * parsing. 187 | * 188 | * @see Requests::request() 189 | * 190 | * @throws Requests_Exception On invalid URLs (`nonhttp`) 191 | * 192 | * @param string $url URL to request 193 | * @param array $headers Extra headers to send with the request 194 | * @param array $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests 195 | * @param string $type HTTP request type (use Requests constants) 196 | * @param array $options Options for the request (see {@see Requests::request}) 197 | * @return Requests_Response 198 | */ 199 | public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) { 200 | $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); 201 | 202 | return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); 203 | } 204 | 205 | /** 206 | * Send multiple HTTP requests simultaneously 207 | * 208 | * @see Requests::request_multiple() 209 | * 210 | * @param array $requests Requests data (see {@see Requests::request_multiple}) 211 | * @param array $options Global and default options (see {@see Requests::request}) 212 | * @return array Responses (either Requests_Response or a Requests_Exception object) 213 | */ 214 | public function request_multiple($requests, $options = array()) { 215 | foreach ($requests as $key => $request) { 216 | $requests[$key] = $this->merge_request($request, false); 217 | } 218 | 219 | $options = array_merge($this->options, $options); 220 | 221 | // Disallow forcing the type, as that's a per request setting 222 | unset($options['type']); 223 | 224 | return Requests::request_multiple($requests, $options); 225 | } 226 | 227 | /** 228 | * Merge a request's data with the default data 229 | * 230 | * @param array $request Request data (same form as {@see request_multiple}) 231 | * @param boolean $merge_options Should we merge options as well? 232 | * @return array Request data 233 | */ 234 | protected function merge_request($request, $merge_options = true) { 235 | if ($this->url !== null) { 236 | $request['url'] = Requests_IRI::absolutize($this->url, $request['url']); 237 | $request['url'] = $request['url']->uri; 238 | } 239 | $request['headers'] = array_merge($this->headers, $request['headers']); 240 | 241 | if (is_array($request['data']) && is_array($this->data)) { 242 | $request['data'] = array_merge($this->data, $request['data']); 243 | } 244 | 245 | if ($merge_options !== false) { 246 | $request['options'] = array_merge($this->options, $request['options']); 247 | 248 | // Disallow forcing the type, as that's a per request setting 249 | unset($request['options']['type']); 250 | } 251 | return $request; 252 | } 253 | } -------------------------------------------------------------------------------- /httpUtils/Requests/Transport.php: -------------------------------------------------------------------------------- 1 | version = $curl['version']; 64 | $this->fp = curl_init(); 65 | 66 | curl_setopt($this->fp, CURLOPT_HEADER, false); 67 | curl_setopt($this->fp, CURLOPT_RETURNTRANSFER, 1); 68 | if (version_compare($this->version, '7.10.5', '>=')) { 69 | curl_setopt($this->fp, CURLOPT_ENCODING, ''); 70 | } 71 | if (version_compare($this->version, '7.19.4', '>=')) { 72 | curl_setopt($this->fp, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); 73 | } 74 | } 75 | 76 | /** 77 | * Perform a request 78 | * 79 | * @throws Requests_Exception On a cURL error (`curlerror`) 80 | * 81 | * @param string $url URL to request 82 | * @param array $headers Associative array of request headers 83 | * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 84 | * @param array $options Request options, see {@see Requests::response()} for documentation 85 | * @return string Raw HTTP result 86 | */ 87 | public function request($url, $headers = array(), $data = array(), $options = array()) { 88 | $this->setup_handle($url, $headers, $data, $options); 89 | 90 | $options['hooks']->dispatch('curl.before_send', array(&$this->fp)); 91 | 92 | if ($options['filename'] !== false) { 93 | $this->stream_handle = fopen($options['filename'], 'wb'); 94 | curl_setopt($this->fp, CURLOPT_FILE, $this->stream_handle); 95 | } 96 | 97 | if (isset($options['verify'])) { 98 | if ($options['verify'] === false) { 99 | curl_setopt($this->fp, CURLOPT_SSL_VERIFYHOST, 0); 100 | curl_setopt($this->fp, CURLOPT_SSL_VERIFYPEER, 0); 101 | 102 | } elseif (is_string($options['verify'])) { 103 | curl_setopt($this->fp, CURLOPT_CAINFO, $options['verify']); 104 | } 105 | } 106 | 107 | if (isset($options['verifyname']) && $options['verifyname'] === false) { 108 | curl_setopt($this->fp, CURLOPT_SSL_VERIFYHOST, 0); 109 | } 110 | 111 | $response = curl_exec($this->fp); 112 | 113 | $options['hooks']->dispatch('curl.after_send', array(&$fake_headers)); 114 | 115 | if (curl_errno($this->fp) === 23 || curl_errno($this->fp) === 61) { 116 | curl_setopt($this->fp, CURLOPT_ENCODING, 'none'); 117 | $response = curl_exec($this->fp); 118 | } 119 | 120 | $this->process_response($response, $options); 121 | return $this->headers; 122 | } 123 | 124 | /** 125 | * Send multiple requests simultaneously 126 | * 127 | * @param array $requests Request data 128 | * @param array $options Global options 129 | * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well) 130 | */ 131 | public function request_multiple($requests, $options) { 132 | $multihandle = curl_multi_init(); 133 | $subrequests = array(); 134 | $subhandles = array(); 135 | 136 | $class = get_class($this); 137 | foreach ($requests as $id => $request) { 138 | $subrequests[$id] = new $class(); 139 | $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); 140 | $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id])); 141 | curl_multi_add_handle($multihandle, $subhandles[$id]); 142 | } 143 | 144 | $completed = 0; 145 | $responses = array(); 146 | 147 | $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle)); 148 | 149 | do { 150 | $active = false; 151 | 152 | do { 153 | $status = curl_multi_exec($multihandle, $active); 154 | } 155 | while ($status === CURLM_CALL_MULTI_PERFORM); 156 | 157 | $to_process = array(); 158 | 159 | // Read the information as needed 160 | while ($done = curl_multi_info_read($multihandle)) { 161 | $key = array_search($done['handle'], $subhandles, true); 162 | if (!isset($to_process[$key])) { 163 | $to_process[$key] = $done; 164 | } 165 | } 166 | 167 | // Parse the finished requests before we start getting the new ones 168 | foreach ($to_process as $key => $done) { 169 | $options = $requests[$key]['options']; 170 | $responses[$key] = $subrequests[$key]->process_response(curl_multi_getcontent($done['handle']), $options); 171 | 172 | $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key])); 173 | 174 | curl_multi_remove_handle($multihandle, $done['handle']); 175 | curl_close($done['handle']); 176 | 177 | if (!is_string($responses[$key])) { 178 | $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key)); 179 | } 180 | $completed++; 181 | } 182 | } 183 | while ($active || $completed < count($subrequests)); 184 | 185 | $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle)); 186 | 187 | curl_multi_close($multihandle); 188 | 189 | return $responses; 190 | } 191 | 192 | /** 193 | * Get the cURL handle for use in a multi-request 194 | * 195 | * @param string $url URL to request 196 | * @param array $headers Associative array of request headers 197 | * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 198 | * @param array $options Request options, see {@see Requests::response()} for documentation 199 | * @return resource Subrequest's cURL handle 200 | */ 201 | public function &get_subrequest_handle($url, $headers, $data, $options) { 202 | $this->setup_handle($url, $headers, $data, $options); 203 | 204 | if ($options['filename'] !== false) { 205 | $this->stream_handle = fopen($options['filename'], 'wb'); 206 | curl_setopt($this->fp, CURLOPT_FILE, $this->stream_handle); 207 | } 208 | 209 | return $this->fp; 210 | } 211 | 212 | /** 213 | * Setup the cURL handle for the given data 214 | * 215 | * @param string $url URL to request 216 | * @param array $headers Associative array of request headers 217 | * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 218 | * @param array $options Request options, see {@see Requests::response()} for documentation 219 | */ 220 | protected function setup_handle($url, $headers, $data, $options) { 221 | $options['hooks']->dispatch('curl.before_request', array(&$this->fp)); 222 | 223 | $headers = Requests::flatten($headers); 224 | if (in_array($options['type'], array(Requests::HEAD, Requests::GET, Requests::DELETE)) & !empty($data)) { 225 | $url = self::format_get($url, $data); 226 | } 227 | elseif (!empty($data) && !is_string($data)) { 228 | $data = http_build_query($data, null, '&'); 229 | } 230 | 231 | switch ($options['type']) { 232 | case Requests::POST: 233 | curl_setopt($this->fp, CURLOPT_POST, true); 234 | curl_setopt($this->fp, CURLOPT_POSTFIELDS, $data); 235 | break; 236 | case Requests::PATCH: 237 | case Requests::PUT: 238 | curl_setopt($this->fp, CURLOPT_CUSTOMREQUEST, $options['type']); 239 | curl_setopt($this->fp, CURLOPT_POSTFIELDS, $data); 240 | break; 241 | case Requests::DELETE: 242 | curl_setopt($this->fp, CURLOPT_CUSTOMREQUEST, 'DELETE'); 243 | break; 244 | case Requests::HEAD: 245 | curl_setopt($this->fp, CURLOPT_NOBODY, true); 246 | break; 247 | } 248 | 249 | curl_setopt($this->fp, CURLOPT_URL, $url); 250 | curl_setopt($this->fp, CURLOPT_TIMEOUT, $options['timeout']); 251 | curl_setopt($this->fp, CURLOPT_CONNECTTIMEOUT, $options['timeout']); 252 | curl_setopt($this->fp, CURLOPT_REFERER, $url); 253 | curl_setopt($this->fp, CURLOPT_USERAGENT, $options['useragent']); 254 | curl_setopt($this->fp, CURLOPT_HTTPHEADER, $headers); 255 | 256 | if (true === $options['blocking']) { 257 | curl_setopt($this->fp, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers')); 258 | } 259 | } 260 | 261 | public function process_response($response, $options) { 262 | if ($options['blocking'] === false) { 263 | curl_close($this->fp); 264 | $fake_headers = ''; 265 | $options['hooks']->dispatch('curl.after_request', array(&$fake_headers)); 266 | return false; 267 | } 268 | if ($options['filename'] !== false) { 269 | fclose($this->stream_handle); 270 | $this->headers = trim($this->headers); 271 | } 272 | else { 273 | $this->headers .= $response; 274 | } 275 | 276 | if (curl_errno($this->fp)) { 277 | throw new Requests_Exception('cURL error ' . curl_errno($this->fp) . ': ' . curl_error($this->fp), 'curlerror', $this->fp); 278 | return; 279 | } 280 | $this->info = curl_getinfo($this->fp); 281 | 282 | curl_close($this->fp); 283 | $options['hooks']->dispatch('curl.after_request', array(&$this->headers)); 284 | return $this->headers; 285 | } 286 | 287 | /** 288 | * Collect the headers as they are received 289 | * 290 | * @param resource $handle cURL resource 291 | * @param string $headers Header string 292 | * @return integer Length of provided header 293 | */ 294 | protected function stream_headers($handle, $headers) { 295 | // Why do we do this? cURL will send both the final response and any 296 | // interim responses, such as a 100 Continue. We don't need that. 297 | // (We may want to keep this somewhere just in case) 298 | if ($this->done_headers) { 299 | $this->headers = ''; 300 | $this->done_headers = false; 301 | } 302 | $this->headers .= $headers; 303 | 304 | if ($headers === "\r\n") { 305 | $this->done_headers = true; 306 | } 307 | return strlen($headers); 308 | } 309 | 310 | /** 311 | * Format a URL given GET data 312 | * 313 | * @param string $url 314 | * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query} 315 | * @return string URL with data 316 | */ 317 | protected static function format_get($url, $data) { 318 | if (!empty($data)) { 319 | $url_parts = parse_url($url); 320 | if (empty($url_parts['query'])) { 321 | $query = $url_parts['query'] = ''; 322 | } 323 | else { 324 | $query = $url_parts['query']; 325 | } 326 | 327 | $query .= '&' . http_build_query($data, null, '&'); 328 | $query = trim($query, '&'); 329 | 330 | if (empty($url_parts['query'])) { 331 | $url .= '?' . $query; 332 | } 333 | else { 334 | $url = str_replace($url_parts['query'], $query, $url); 335 | } 336 | } 337 | return $url; 338 | } 339 | 340 | /** 341 | * Whether this transport is valid 342 | * 343 | * @codeCoverageIgnore 344 | * @return boolean True if the transport is valid, false otherwise. 345 | */ 346 | public static function test() { 347 | return (function_exists('curl_init') && function_exists('curl_exec')); 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /httpUtils/Requests/Transport/fsockopen.php: -------------------------------------------------------------------------------- 1 | dispatch('fsockopen.before_request'); 46 | 47 | $url_parts = parse_url($url); 48 | $host = $url_parts['host']; 49 | $context = stream_context_create(); 50 | $verifyname = false; 51 | 52 | // HTTPS support 53 | if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { 54 | $remote_socket = 'ssl://' . $host; 55 | $url_parts['port'] = 443; 56 | 57 | $context_options = array( 58 | 'verify_peer' => true, 59 | // 'CN_match' => $host, 60 | 'capture_peer_cert' => true 61 | ); 62 | $verifyname = true; 63 | 64 | // SNI, if enabled (OpenSSL >=0.9.8j) 65 | if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) { 66 | $context_options['SNI_enabled'] = true; 67 | if (isset($options['verifyname']) && $options['verifyname'] === false) { 68 | $context_options['SNI_enabled'] = false; 69 | } 70 | } 71 | 72 | if (isset($options['verify'])) { 73 | if ($options['verify'] === false) { 74 | $context_options['verify_peer'] = false; 75 | } elseif (is_string($options['verify'])) { 76 | $context_options['cafile'] = $options['verify']; 77 | } 78 | } 79 | 80 | if (isset($options['verifyname']) && $options['verifyname'] === false) { 81 | $verifyname = false; 82 | } 83 | 84 | stream_context_set_option($context, array('ssl' => $context_options)); 85 | } 86 | else { 87 | $remote_socket = 'tcp://' . $host; 88 | } 89 | 90 | $proxy = isset( $options['proxy'] ); 91 | $proxy_auth = $proxy && isset( $options['proxy_username'] ) && isset( $options['proxy_password'] ); 92 | 93 | if (!isset($url_parts['port'])) { 94 | $url_parts['port'] = 80; 95 | } 96 | $remote_socket .= ':' . $url_parts['port']; 97 | 98 | set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE); 99 | 100 | $options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket)); 101 | 102 | $fp = stream_socket_client($remote_socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $context); 103 | 104 | restore_error_handler(); 105 | 106 | if ($verifyname) { 107 | if (!$this->verify_certificate_from_context($host, $context)) { 108 | throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); 109 | } 110 | } 111 | 112 | if (!$fp) { 113 | if ($errno === 0) { 114 | // Connection issue 115 | throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); 116 | } 117 | else { 118 | throw new Requests_Exception($errstr, 'fsockopenerror'); 119 | return; 120 | } 121 | } 122 | 123 | $request_body = ''; 124 | $out = ''; 125 | switch ($options['type']) { 126 | case Requests::POST: 127 | case Requests::PUT: 128 | case Requests::PATCH: 129 | if (isset($url_parts['path'])) { 130 | $path = $url_parts['path']; 131 | if (isset($url_parts['query'])) { 132 | $path .= '?' . $url_parts['query']; 133 | } 134 | } 135 | else { 136 | $path = '/'; 137 | } 138 | 139 | $options['hooks']->dispatch( 'fsockopen.remote_host_path', array( &$path, $url ) ); 140 | $out = $options['type'] . " $path HTTP/1.0\r\n"; 141 | 142 | if (is_array($data)) { 143 | $request_body = http_build_query($data, null, '&'); 144 | } 145 | else { 146 | $request_body = $data; 147 | } 148 | if (empty($headers['Content-Length'])) { 149 | $headers['Content-Length'] = strlen($request_body); 150 | } 151 | if (empty($headers['Content-Type'])) { 152 | $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; 153 | } 154 | break; 155 | case Requests::HEAD: 156 | case Requests::GET: 157 | case Requests::DELETE: 158 | $path = self::format_get($url_parts, $data); 159 | $options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url)); 160 | $out = $options['type'] . " $path HTTP/1.0\r\n"; 161 | break; 162 | } 163 | $out .= "Host: {$url_parts['host']}"; 164 | 165 | if ($url_parts['port'] !== 80) { 166 | $out .= ":{$url_parts['port']}"; 167 | } 168 | $out .= "\r\n"; 169 | 170 | $out .= "User-Agent: {$options['useragent']}\r\n"; 171 | $accept_encoding = $this->accept_encoding(); 172 | if (!empty($accept_encoding)) { 173 | $out .= "Accept-Encoding: $accept_encoding\r\n"; 174 | } 175 | 176 | $headers = Requests::flatten($headers); 177 | 178 | if (!empty($headers)) { 179 | $out .= implode($headers, "\r\n") . "\r\n"; 180 | } 181 | 182 | $options['hooks']->dispatch('fsockopen.after_headers', array(&$out)); 183 | 184 | if (substr($out, -2) !== "\r\n") { 185 | $out .= "\r\n"; 186 | } 187 | 188 | $out .= "Connection: Close\r\n\r\n" . $request_body; 189 | 190 | $options['hooks']->dispatch('fsockopen.before_send', array(&$out)); 191 | 192 | fwrite($fp, $out); 193 | $options['hooks']->dispatch('fsockopen.after_send', array(&$fake_headers)); 194 | 195 | if (!$options['blocking']) { 196 | fclose($fp); 197 | $fake_headers = ''; 198 | $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers)); 199 | return ''; 200 | } 201 | stream_set_timeout($fp, $options['timeout']); 202 | 203 | $this->info = stream_get_meta_data($fp); 204 | 205 | $this->headers = ''; 206 | $this->info = stream_get_meta_data($fp); 207 | if (!$options['filename']) { 208 | while (!feof($fp)) { 209 | $this->info = stream_get_meta_data($fp); 210 | if ($this->info['timed_out']) { 211 | throw new Requests_Exception('fsocket timed out', 'timeout'); 212 | } 213 | 214 | $this->headers .= fread($fp, 1160); 215 | } 216 | } 217 | else { 218 | $download = fopen($options['filename'], 'wb'); 219 | $doingbody = false; 220 | $response = ''; 221 | while (!feof($fp)) { 222 | $this->info = stream_get_meta_data($fp); 223 | if ($this->info['timed_out']) { 224 | throw new Requests_Exception('fsocket timed out', 'timeout'); 225 | } 226 | 227 | $block = fread($fp, 1160); 228 | if ($doingbody) { 229 | fwrite($download, $block); 230 | } 231 | else { 232 | $response .= $block; 233 | if (strpos($response, "\r\n\r\n")) { 234 | list($this->headers, $block) = explode("\r\n\r\n", $response, 2); 235 | $doingbody = true; 236 | fwrite($download, $block); 237 | } 238 | } 239 | } 240 | fclose($download); 241 | } 242 | fclose($fp); 243 | 244 | $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers)); 245 | return $this->headers; 246 | } 247 | 248 | /** 249 | * Send multiple requests simultaneously 250 | * 251 | * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request} 252 | * @param array $options Global options, see {@see Requests::response()} for documentation 253 | * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well) 254 | */ 255 | public function request_multiple($requests, $options) { 256 | $responses = array(); 257 | $class = get_class($this); 258 | foreach ($requests as $id => $request) { 259 | try { 260 | $handler = new $class(); 261 | $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); 262 | 263 | $request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request)); 264 | } 265 | catch (Requests_Exception $e) { 266 | $responses[$id] = $e; 267 | } 268 | 269 | if (!is_string($responses[$id])) { 270 | $request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id)); 271 | } 272 | } 273 | 274 | return $responses; 275 | } 276 | 277 | /** 278 | * Retrieve the encodings we can accept 279 | * 280 | * @return string Accept-Encoding header value 281 | */ 282 | protected static function accept_encoding() { 283 | $type = array(); 284 | if (function_exists('gzinflate')) { 285 | $type[] = 'deflate;q=1.0'; 286 | } 287 | 288 | if (function_exists('gzuncompress')) { 289 | $type[] = 'compress;q=0.5'; 290 | } 291 | 292 | $type[] = 'gzip;q=0.5'; 293 | 294 | return implode(', ', $type); 295 | } 296 | 297 | /** 298 | * Format a URL given GET data 299 | * 300 | * @param array $url_parts 301 | * @param array|object $data Data to build query using, see {@see http://php.net/http_build_query} 302 | * @return string URL with data 303 | */ 304 | protected static function format_get($url_parts, $data) { 305 | if (!empty($data)) { 306 | if (empty($url_parts['query'])) 307 | $url_parts['query'] = ''; 308 | 309 | $url_parts['query'] .= '&' . http_build_query($data, null, '&'); 310 | $url_parts['query'] = trim($url_parts['query'], '&'); 311 | } 312 | if (isset($url_parts['path'])) { 313 | if (isset($url_parts['query'])) { 314 | $get = $url_parts['path'] . '?' . $url_parts['query']; 315 | } 316 | else { 317 | $get = $url_parts['path']; 318 | } 319 | } 320 | else { 321 | $get = '/'; 322 | } 323 | return $get; 324 | } 325 | 326 | /** 327 | * Error handler for stream_socket_client() 328 | * 329 | * @param int $errno Error number (e.g. E_WARNING) 330 | * @param string $errstr Error message 331 | */ 332 | public function connect_error_handler($errno, $errstr) { 333 | // Double-check we can handle it 334 | if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) { 335 | // Return false to indicate the default error handler should engage 336 | return false; 337 | } 338 | 339 | $this->connect_error .= $errstr . "\n"; 340 | return true; 341 | } 342 | 343 | /** 344 | * Verify the certificate against common name and subject alternative names 345 | * 346 | * Unfortunately, PHP doesn't check the certificate against the alternative 347 | * names, leading things like 'https://www.github.com/' to be invalid. 348 | * Instead 349 | * 350 | * @see http://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 351 | * 352 | * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) 353 | * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) 354 | * @param string $host Host name to verify against 355 | * @param resource $context Stream context 356 | * @return bool 357 | */ 358 | public function verify_certificate_from_context($host, $context) { 359 | $meta = stream_context_get_options($context); 360 | 361 | // If we don't have SSL options, then we couldn't make the connection at 362 | // all 363 | if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { 364 | throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error'); 365 | } 366 | 367 | $cert = openssl_x509_parse($meta['ssl']['peer_certificate']); 368 | 369 | return Requests_SSL::verify_certificate($host, $cert); 370 | } 371 | 372 | /** 373 | * Whether this transport is valid 374 | * 375 | * @codeCoverageIgnore 376 | * @return boolean True if the transport is valid, false otherwise. 377 | */ 378 | public static function test() { 379 | return function_exists('fsockopen'); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /httpUtils/Requests/Utility/CaseInsensitiveDictionary.php: -------------------------------------------------------------------------------- 1 | data[$key]); 32 | } 33 | 34 | /** 35 | * Get the value for the item 36 | * 37 | * @param string $key Item key 38 | * @return string Item value 39 | */ 40 | public function offsetGet($key) { 41 | $key = strtolower($key); 42 | if (!isset($this->data[$key])) 43 | return null; 44 | 45 | return $this->data[$key]; 46 | } 47 | 48 | /** 49 | * Set the given item 50 | * 51 | * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`) 52 | * 53 | * @param string $key Item name 54 | * @param string $value Item value 55 | */ 56 | public function offsetSet($key, $value) { 57 | if ($key === null) { 58 | throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset'); 59 | } 60 | 61 | $key = strtolower($key); 62 | $this->data[$key] = $value; 63 | } 64 | 65 | /** 66 | * Unset the given header 67 | * 68 | * @param string $key 69 | */ 70 | public function offsetUnset($key) { 71 | unset($this->data[strtolower($key)]); 72 | } 73 | 74 | /** 75 | * Get an iterator for the data 76 | * 77 | * @return ArrayIterator 78 | */ 79 | public function getIterator() { 80 | return new ArrayIterator($this->data); 81 | } 82 | 83 | /** 84 | * Get the headers as an array 85 | * 86 | * @return array Header data 87 | */ 88 | public function getAll() { 89 | return $this->data; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /httpUtils/Requests/Utility/FilteredIterator.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 26 | } 27 | 28 | /** 29 | * Get the current item's value after filtering 30 | * 31 | * @return string 32 | */ 33 | public function current() { 34 | $value = parent::current(); 35 | $value = call_user_func($this->callback, $value); 36 | return $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | setUuid(); 18 | 19 | echo $weChat->getQRCode(); 20 | 21 | echo '
'; 22 | 23 | sleep(2); 24 | 25 | 26 | while (true) { 27 | 28 | if ($weChat->waitForLogin() == 200) { 29 | 30 | break; 31 | 32 | } 33 | 34 | } 35 | 36 | 37 | echo '
'; 38 | 39 | if (!$weChat->login()) { 40 | 41 | echo '登录失败!
'; 42 | 43 | return; 44 | 45 | } else { 46 | 47 | echo '登录成功!
'; 48 | 49 | } 50 | 51 | if (!$weChat->weChatInitial()) { 52 | 53 | echo '初始化失败!
'; 54 | 55 | return; 56 | 57 | } 58 | 59 | $memberList = array_values($weChat->webwxgetcontact()); 60 | 61 | $memberCount = count($memberList) - 1; 62 | 63 | echo "你的微信里目前有 ".$memberCount." 个好友
"; 64 | 65 | $groupNumber = ceil($memberCount/MAX_GROUP_NUM); 66 | 67 | 68 | 69 | $chatRoomName = ''; 70 | for ($i = 0 ;$i < $groupNumber ;$i++){ 71 | $usernames = array(); 72 | $nicknames = array(); 73 | 74 | for($j = 0 ;$j < MAX_GROUP_NUM ;$j++){ 75 | 76 | if(($i * MAX_GROUP_NUM + $j) >= $memberCount){ 77 | break; 78 | } 79 | $member = $memberList[$i + MAX_GROUP_NUM + $j]; 80 | $usernames[] = $member['UserName']; 81 | $nicknames[] = $member['NickName']; 82 | } 83 | //TODO 84 | if($chatRoomName == ''){ 85 | 86 | $chatRoomName = $weChat->createChatRoom($usernames); 87 | 88 | }else{ 89 | 90 | $weChat->addMember($chatRoomName,$usernames); 91 | 92 | sleep(2); 93 | 94 | } 95 | 96 | $weChat->deleteMember($chatRoomName,$usernames); 97 | 98 | sleep(2); 99 | 100 | } 101 | 102 | echo '
---------------:当前删除你的好友列表如下:---------------
'; 103 | 104 | $deleteList = $weChat->getDeleteList(); 105 | 106 | $resultNames = ''; 107 | 108 | if(empty($deleteList)){ 109 | 110 | echo '没有任何人删除了你.'; 111 | return; 112 | } 113 | 114 | foreach ($memberList as $key => $member){ 115 | 116 | if(in_array($member['UserName'],$deleteList)){ 117 | $resultNames .= '|'.$member['NickName']; 118 | } 119 | 120 | } 121 | 122 | 123 | echo $resultNames.'
'; 124 | 125 | 126 | 127 | 128 | --------------------------------------------------------------------------------