├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 | true
66 |
67 |
68 |
69 |
70 |
71 |
72 |
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 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | 1452221001403
133 |
134 | 1452221001403
135 |
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 |
163 |
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 |
--------------------------------------------------------------------------------