Fixes and cleanups
[mailer.git] / inc / http-functions.php
1 <?php
2 /************************************************************************
3  * Mailer v0.2.1-FINAL                                Start: 03/08/2011 *
4  * ===================                          Last change: 03/08/2011 *
5  *                                                                      *
6  * -------------------------------------------------------------------- *
7  * File              : http-functions.php                               *
8  * -------------------------------------------------------------------- *
9  * Short description : HTTP-related functions                           *
10  * -------------------------------------------------------------------- *
11  * Kurzbeschreibung  : HTTP-relevante Funktionen                        *
12  * -------------------------------------------------------------------- *
13  * $Revision::                                                        $ *
14  * $Date::                                                            $ *
15  * $Tag:: 0.2.1-FINAL                                                 $ *
16  * $Author::                                                          $ *
17  * -------------------------------------------------------------------- *
18  * Copyright (c) 2003 - 2009 by Roland Haeder                           *
19  * Copyright (c) 2009 - 2011 by Mailer Developer Team                   *
20  * For more information visit: http://mxchange.org                      *
21  *                                                                      *
22  * This program is free software; you can redistribute it and/or modify *
23  * it under the terms of the GNU General Public License as published by *
24  * the Free Software Foundation; either version 2 of the License, or    *
25  * (at your option) any later version.                                  *
26  *                                                                      *
27  * This program is distributed in the hope that it will be useful,      *
28  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
29  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
30  * GNU General Public License for more details.                         *
31  *                                                                      *
32  * You should have received a copy of the GNU General Public License    *
33  * along with this program; if not, write to the Free Software          *
34  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,               *
35  * MA  02110-1301  USA                                                  *
36  ************************************************************************/
37
38 // Some security stuff...
39 if (!defined('__SECURITY')) {
40         die();
41 } // END - if
42
43 // Sends out all headers required for HTTP/1.1 reply
44 function sendHttpHeaders () {
45         // Used later
46         $now = gmdate('D, d M Y H:i:s') . ' GMT';
47
48         // Send HTTP header
49         sendHeader('HTTP/1.1 ' . getHttpStatus());
50
51         // General headers for no caching
52         sendHeader('Expires: ' . $now); // RFC2616 - Section 14.21
53         sendHeader('Last-Modified: ' . $now);
54         sendHeader('Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0'); // HTTP/1.1
55         sendHeader('Pragma: no-cache'); // HTTP/1.0
56         sendHeader('Connection: Close');
57         sendHeader('Content-Type: ' . getContentType() . '; charset=UTF-8');
58         sendHeader('Content-Language: ' . getLanguage());
59 }
60
61 // Checks wether the URL is full-qualified (http[s]:// + hostname [+ request data])
62 function isFullQualifiedUrl ($url) {
63         // Do we have cache?
64         if (!isset($GLOBALS[__FUNCTION__][$url])) {
65                 // Determine it
66                 $GLOBALS[__FUNCTION__][$url] = ((substr($url, 0, 7) == 'http://') || (substr($url, 0, 8) == 'https://'));
67         } // END - if
68
69         // Return cache
70         return $GLOBALS[__FUNCTION__][$url];
71 }
72
73 // Generates the full GET URL from given base URL and data array
74 function generateGetUrlFromBaseUrlData ($baseUrl, $requestData = array()) {
75         // Init URL
76         $getUrl = $baseUrl;
77
78         // Is it full-qualified?
79         if (!isFullQualifiedUrl($getUrl)) {
80                 // Need to prepend a slash?
81                 if (substr($getUrl, 0, 1) != '/') {
82                         // Prepend it
83                         $getUrl = '/' . $getUrl;
84                 } // END - if
85
86                 // Prepend http://hostname from mxchange.org server
87                 $getUrl = getServerUrl() . $getUrl;
88         } // END - if
89
90         // Add data
91         $body = http_build_query($requestData, '', '&');
92
93         // There should be data, else we don't need to extend $baseUrl with $body
94         if (!empty($body)) {
95                 // Do we have a question-mark in the script?
96                 if (!isInString('?', $baseUrl)) {
97                         // No, so first char must be question mark
98                         $body = '?' . $body;
99                 } else {
100                         // Ok, add &
101                         $body = '&' . $body;
102                 }
103
104                 // Add script data
105                 $getUrl .= $body;
106
107                 // Remove trailed & to make it more conform
108                 if (substr($getUrl, -1, 1) == '&') {
109                         $getUrl = substr($getUrl, 0, -1);
110                 } // END - if
111         } // END - if
112
113         // Return it
114         return $getUrl;
115 }
116
117 // Removes http[s]://<hostname> from given url
118 function removeHttpHostNameFromUrl ($url) {
119         // Remove http[s]://
120         $remove = explode(':', $url);
121         $remove = explode('/', substr($remove[1], 3));
122
123         // Remove the first element (should be the hostname)
124         unset($remove[0]);
125
126         // implode() back all other elements and prepend a slash
127         $url = '/' . implode('/', $remove);
128
129         // Return prepared URL
130         return $url;
131 }
132
133 // Send a HEAD request
134 function sendHeadRequest ($baseUrl, $requestData = array()) {
135         // Generate full GET URL
136         $getUrl = generateGetUrlFromBaseUrlData($baseUrl, $requestData);
137
138         // Do we have http[s]:// in front of the URL?
139         if (isFullQualifiedUrl($getUrl)) {
140                 // Remove http[s]://<hostname> from url
141                 $getUrl = removeHttpHostNameFromUrl($getUrl);
142         } elseif (substr($getUrl, 0, 1) != '/') {
143                 // Prepend a slash
144                 $getUrl = '/' . $getUrl;
145         }
146
147         // Extract hostname and port from script
148         $host = extractHostnameFromUrl($baseUrl);
149
150         // Generate HEAD request header
151         $request  = 'HEAD ' . trim($getUrl) . ' HTTP/1.1' . getConfig('HTTP_EOL');
152         $request .= 'Host: ' . $host . getConfig('HTTP_EOL');
153         $request .= 'Referer: ' . getUrl() . '/admin.php' . getConfig('HTTP_EOL');
154         if (isConfigEntrySet('FULL_VERSION')) {
155                 $request .= 'User-Agent: ' . getTitle() . '/' . getFullVersion() . getConfig('HTTP_EOL');
156         } else {
157                 $request .= 'User-Agent: ' . getTitle() . '/' . getConfig('VERSION') . getConfig('HTTP_EOL');
158         }
159         $request .= 'Accept: image/png,image/*;q=0.8,text/plain,text/html,*/*;q=0.5' . getConfig('HTTP_EOL');
160         $request .= 'Accept-Charset: UTF-8,*' . getConfig('HTTP_EOL');
161         $request .= 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' . getConfig('HTTP_EOL');
162         $request .= 'Connection: close' . getConfig('HTTP_EOL');
163         $request .= getConfig('HTTP_EOL');
164
165         // Send the raw request
166         $response = sendRawRequest($host, $request);
167
168         // Return the result to the caller function
169         return $response;
170 }
171
172 // Send a GET request
173 function sendGetRequest ($baseUrl, $requestData = array(), $removeHeader = false) {
174         // Generate full GET URL
175         $getUrl = generateGetUrlFromBaseUrlData($baseUrl, $requestData);
176
177         // Do we have http[s]:// in front of the URL?
178         if (isFullQualifiedUrl($getUrl)) {
179                 // Remove http[s]://<hostname> from url
180                 $getUrl = removeHttpHostNameFromUrl($getUrl);
181         } elseif (substr($getUrl, 0, 1) != '/') {
182                 // Prepend a slash
183                 $getUrl = '/' . $getUrl;
184         }
185
186         // Extract hostname and port from script
187         $host = extractHostnameFromUrl($baseUrl);
188
189         // Generate GET request header
190         $request  = 'GET ' . trim($getUrl) . ' HTTP/1.1' . getConfig('HTTP_EOL');
191         $request .= 'Host: ' . $host . getConfig('HTTP_EOL');
192         $request .= 'Referer: ' . getUrl() . '/admin.php' . getConfig('HTTP_EOL');
193         if (isConfigEntrySet('FULL_VERSION')) {
194                 $request .= 'User-Agent: ' . getTitle() . '/' . getFullVersion() . getConfig('HTTP_EOL');
195         } else {
196                 $request .= 'User-Agent: ' . getTitle() . '/' . getConfig('VERSION') . getConfig('HTTP_EOL');
197         }
198         $request .= 'Accept: image/png,image/*;q=0.8,text/plain,text/html,*/*;q=0.5' . getConfig('HTTP_EOL');
199         $request .= 'Accept-Charset: UTF-8,*' . getConfig('HTTP_EOL');
200         $request .= 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' . getConfig('HTTP_EOL');
201         $request .= 'Connection: close' . getConfig('HTTP_EOL');
202         $request .= getConfig('HTTP_EOL');
203
204         // Send the raw request
205         $response = sendRawRequest($host, $request);
206
207         // Should we remove header lines?
208         if ($removeHeader === true) {
209                 // Okay, remove them
210                 $response = removeHttpHeaderFromResponse($response);
211         } // END - if
212
213         // Return the result to the caller function
214         return $response;
215 }
216
217 // Send a POST request
218 function sendPostRequest ($baseUrl, $requestData, $removeHeader = false) {
219         // Copy baseUrl to getUrl
220         $getUrl = $baseUrl;
221
222         // Do we have http[s]:// in front of the URL?
223         if (isFullQualifiedUrl($getUrl)) {
224                 // Remove http[s]://<hostname> from url
225                 $getUrl = removeHttpHostNameFromUrl($getUrl);
226         } elseif (substr($getUrl, 0, 1) != '/') {
227                 // Prepend a slash
228                 $getUrl = '/' . $getUrl;
229         }
230
231         // Extract host name from script
232         $host = extractHostnameFromUrl($baseUrl);
233
234         // Construct request body
235         $body = http_build_query($requestData, '', '&');
236
237         // Generate POST request header
238         $request  = 'POST ' . trim($baseUrl) . ' HTTP/1.0' . getConfig('HTTP_EOL');
239         $request .= 'Host: ' . $host . getConfig('HTTP_EOL');
240         $request .= 'Referer: ' . getUrl() . '/admin.php' . getConfig('HTTP_EOL');
241         if (isConfigEntrySet('FULL_VERSION')) {
242                 $request .= 'User-Agent: ' . getTitle() . '/' . getFullVersion() . getConfig('HTTP_EOL');
243         } else {
244                 $request .= 'User-Agent: ' . getTitle() . '/' . getConfig('VERSION') . getConfig('HTTP_EOL');
245         }
246         $request .= 'Accept: text/plain;q=0.8' . getConfig('HTTP_EOL');
247         $request .= 'Accept-Charset: UTF-8,*' . getConfig('HTTP_EOL');
248         $request .= 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' . getConfig('HTTP_EOL');
249         $request .= 'Content-Type: application/x-www-form-urlencoded' . getConfig('HTTP_EOL');
250         $request .= 'Content-Length: ' . strlen($body) . getConfig('HTTP_EOL');
251         $request .= 'Connection: close' . getConfig('HTTP_EOL');
252         $request .= getConfig('HTTP_EOL');
253
254         // Add body
255         $request .= $body;
256
257         // Send the raw request
258         $response = sendRawRequest($host, $request);
259
260         // Should we remove header lines?
261         if ($removeHeader === true) {
262                 // Okay, remove them
263                 $response = removeHttpHeaderFromResponse($response);
264         } // END - if
265
266         // Return the result to the caller function
267         return $response;
268 }
269
270 // Sends a raw request (string) to given host (hostnames will be solved)
271 function sendRawRequest ($host, $request) {
272         //* DEBUG: */ die('host='.$host.',request=<pre>'.$request.'</pre>');
273         // Init errno and errdesc with 'all fine' values
274         $errno = '0';
275         $errdesc = '';
276
277         // Default port is 80
278         $port = 80;
279
280         // Initialize array
281         $response = array('', '', '');
282
283         // Default is not to use proxy
284         $useProxy = false;
285
286         // Default is non-broken HTTP server implementation
287         $GLOBALS['is_http_server_broken'] = false;
288
289         // Are proxy settins set?
290         if (isProxyUsed()) {
291                 // Then use it
292                 $useProxy = true;
293         } // END - if
294
295         // Load include
296         loadIncludeOnce('inc/classes/resolver.class.php');
297
298         // Extract port part from host
299         $portArray = explode(':', $host);
300         if (count($portArray) == 2) {
301                 // Extract host and port
302                 $host = $portArray[0];
303                 $port = $portArray[1];
304         } elseif (count($portArray) > 2) {
305                 // This should not happen!
306                 debug_report_bug(__FUNCTION__, __LINE__, 'Invalid ' . $host . '. Please report this to the Mailer-Project team.');
307         }
308
309         // Get resolver instance
310         $resolver = new HostnameResolver();
311
312         // Open connection
313         if ($useProxy === true) {
314                 // Resolve hostname into IP address
315                 $ip = $resolver->resolveHostname(compileRawCode(getProxyHost()));
316
317                 // Connect to host through proxy connection
318                 $fp = fsockopen($ip, bigintval(getProxyPort()), $errno, $errdesc, 30);
319         } else {
320                 // Resolve hostname into IP address
321                 $ip = $resolver->resolveHostname($host);
322
323                 // Connect to host directly
324                 $fp = fsockopen($ip, $port, $errno, $errdesc, 30);
325         }
326
327         // Is there a link?
328         if (!is_resource($fp)) {
329                 // Failed!
330                 logDebugMessage(__FUNCTION__, __LINE__, $errdesc . ' (' . $errno . ')');
331                 return $response;
332         } elseif ((!stream_set_blocking($fp, 0)) || (!stream_set_timeout($fp, 1))) {
333                 // Cannot set non-blocking mode or timeout
334                 logDebugMessage(__FUNCTION__, __LINE__, socket_strerror(socket_last_error()));
335                 return $response;
336         }
337
338         // Do we use proxy?
339         if ($useProxy === true) {
340                 // Setup proxy tunnel
341                 $response = setupProxyTunnel($host, $port, $fp);
342
343                 // If the response is invalid, abort
344                 if ((count($response) == 3) && (empty($response[0])) && (empty($response[1])) && (empty($response[2]))) {
345                         // Invalid response!
346                         logDebugMessage(__FUNCTION__, __LINE__, 'Proxy tunnel not working?');
347                         return $response;
348                 } // END - if
349         } // END - if
350
351         // Write request
352         fwrite($fp, $request);
353
354         // Start counting
355         $start = microtime(true);
356
357         // Read response
358         while (!feof($fp)) {
359                 // Get info from stream
360                 $info = stream_get_meta_data($fp);
361
362                 // Is it timed out? 15 seconds is a really patient...
363                 if (($info['timed_out'] == true) || (microtime(true) - $start) > 15) {
364                         // Timeout
365                         logDebugMessage(__FUNCTION__, __LINE__, 'Timed out to get data from host ' . $host);
366
367                         // Abort here
368                         break;
369                 } // END - if
370
371                 // Get line from stream
372                 $line = fgets($fp, 128);
373
374                 // Ignore empty lines because of non-blocking mode
375                 if (empty($line)) {
376                         // uslepp a little to avoid 100% CPU load
377                         usleep(10);
378
379                         // Skip this
380                         continue;
381                 } // END - if
382
383                 // Check for broken HTTP implementations
384                 if (substr(strtolower($line), 0, 7) == 'server:') {
385                         // Anomic (see http://anomic.de, http://yacy.net) is currently broken
386                         $GLOBALS['is_http_server_broken'] = (count(getArrayKeysFromSubStrArray(strtolower($line), array('anomichttpd'))) > 0);
387                 } // END - if
388
389                 // Add it to response
390                 //* DEBUG: */ print 'line='.$line.'<br />';
391                 $response[] = $line;
392         } // END - while
393
394         // Close socket
395         fclose($fp);
396
397         // Time request if debug-mode is enabled
398         if (isDebugModeEnabled()) {
399                 // Add debug message...
400                 logDebugMessage(__FUNCTION__, __LINE__, 'Request took ' . (microtime(true) - $start) . ' seconds and returned ' . count($response) . ' line(s).');
401         } // END - if
402
403         // Skip first empty lines
404         $resp = $response;
405         foreach ($resp as $idx => $line) {
406                 // Trim space away
407                 $line = trim($line);
408
409                 // Is this line empty?
410                 if (empty($line)) {
411                         // Then remove it
412                         array_shift($response);
413                 } else {
414                         // Abort on first non-empty line
415                         break;
416                 }
417         } // END - foreach
418
419         //* DEBUG: */ debugOutput('<strong>Request:</strong><pre>'.print_r($request, true).'</pre>');
420         //* DEBUG: */ debugOutput('<strong>Response:</strong><pre>'.print_r($response, true).'</pre>');
421
422         // Proxy agent found or something went wrong?
423         if (!isset($response[0])) {
424                 // No response, maybe timeout
425                 $response = array('', '', '');
426                 logDebugMessage(__FUNCTION__, __LINE__, 'Invalid empty response array, maybe timed out?');
427         } elseif ((substr(strtolower($response[0]), 0, 11) == 'proxy-agent') && ($useProxy === true)) {
428                 // Proxy header detected, so remove two lines
429                 array_shift($response);
430                 array_shift($response);
431         } // END - if
432
433         // Was the request successfull?
434         if ((!isInStringIgnoreCase('200 OK', $response[0])) || (empty($response[0]))) {
435                 // Not found / access forbidden
436                 logDebugMessage(__FUNCTION__, __LINE__, 'Unexpected status code ' . $response[0] . ' detected. "200 OK" was expected.');
437                 $response = array('', '', '');
438         } else {
439                 // Check array for chuncked encoding
440                 $response = unchunkHttpResponse($response);
441         } // END - if
442
443         // Return response
444         return $response;
445 }
446
447 // Sets up a proxy tunnel for given hostname and through resource
448 function setupProxyTunnel ($host, $port, $resource) {
449         // Initialize array
450         $response = array('', '', '');
451
452         // Generate CONNECT request header
453         $proxyTunnel  = 'CONNECT ' . $host . ':' . $port . ' HTTP/1.0' . getConfig('HTTP_EOL');
454         $proxyTunnel .= 'Host: ' . $host . getConfig('HTTP_EOL');
455
456         // Use login data to proxy? (username at least!)
457         if (getProxyUsername() != '') {
458                 // Add it as well
459                 $encodedAuth = base64_encode(compileRawCode(getProxyUsername()) . ':' . compileRawCode(getProxyPassword()));
460                 $proxyTunnel .= 'Proxy-Authorization: Basic ' . $encodedAuth . getConfig('HTTP_EOL');
461         } // END - if
462
463         // Add last new-line
464         $proxyTunnel .= getConfig('HTTP_EOL');
465         //* DEBUG: */ debugOutput('<strong>proxyTunnel=</strong><pre>' . $proxyTunnel.'</pre>');
466
467         // Write request
468         fwrite($fp, $proxyTunnel);
469
470         // Got response?
471         if (feof($fp)) {
472                 // No response received
473                 return $response;
474         } // END - if
475
476         // Read the first line
477         $resp = trim(fgets($fp, 10240));
478         $respArray = explode(' ', $resp);
479         if ((strtolower($respArray[0]) !== 'http/1.0') || ($respArray[1] != '200')) {
480                 // Invalid response!
481                 return $response;
482         } // END - if
483
484         // All fine!
485         return $respArray;
486 }
487
488 // Check array for chuncked encoding
489 function unchunkHttpResponse ($response) {
490         // Default is not chunked
491         $isChunked = false;
492
493         // Check if we have chunks
494         foreach ($response as $line) {
495                 // Make lower-case and trim it
496                 $line = trim($line);
497
498                 // Entry found?
499                 if ((isInStringIgnoreCase('transfer-encoding', $line)) && (isInStringIgnoreCase('chunked', $line))) {
500                         // Found!
501                         $isChunked = true;
502                         break;
503                 } // END - if
504         } // END - foreach
505
506         // Is it chunked?
507         if ($isChunked === true) {
508                 // Good, we still have the HTTP headers in there, so we need to get rid
509                 // of them temporarly
510                 //* DEBUG: */ die('<pre>'.htmlentities(print_r(removeHttpHeaderFromResponse($response), true)).'</pre>');
511                 $tempResponse = http_chunked_decode(implode('', removeHttpHeaderFromResponse($response)));
512
513                 // We got a string back from http_chunked_decode(), so we need to convert it back to an array
514                 //* DEBUG: */ die('tempResponse['.strlen($tempResponse).']=<pre>'.replaceReturnNewLine(htmlentities($tempResponse)).'</pre>');
515
516                 // Re-add the headers
517                 $response = merge_array($GLOBALS['http_headers'], stringToArray("\n", $tempResponse));
518         } // END - if
519
520         // Return the unchunked array
521         return $response;
522 }
523
524 // Removes HTTP header lines from a response array (e.g. output from send<Get|Post>Request() )
525 function removeHttpHeaderFromResponse ($response) {
526         // Save headers for later usage
527         $GLOBALS['http_headers'] = array();
528
529         // The first array element has to contain HTTP
530         if ((isset($response[0])) && (substr(strtoupper($response[0]), 0, 5) == 'HTTP/')) {
531                 // Okay, we have headers, now remove them with a second array
532                 $response2 = $response;
533                 foreach ($response as $line) {
534                         // Remove line
535                         array_shift($response2);
536
537                         // Add full line to temporary global array
538                         $GLOBALS['http_headers'][] = $line;
539
540                         // Trim it for testing
541                         $lineTest = trim($line);
542
543                         // Is this line empty?
544                         if (empty($lineTest)) {
545                                 // Then stop here
546                                 break;
547                         } // END - if
548                 } // END - foreach
549
550                 // Write back the array
551                 $response = $response2;
552         } // END - if
553
554         // Return the modified response array
555         return $response;
556 }
557
558 // Returns the flag if a broken HTTP server implementation was detected
559 function isBrokenHttpServerImplentation () {
560         // Determine it
561         $isBroken = ((isset($GLOBALS['is_http_server_broken'])) && ($GLOBALS['is_http_server_broken'] === true));
562
563         // ... and return it
564         return $isBroken;
565 }
566
567 //-----------------------------------------------------------------------------
568 // Automatically re-created functions, all taken from user comments on www.php.net
569 //-----------------------------------------------------------------------------
570
571 if (!function_exists('http_build_query')) {
572         // Taken from documentation on www.php.net, credits to Marco K. (Germany) and some light mods by R.Haeder
573         function http_build_query($requestData, $prefix = '', $sep = '', $key = '') {
574                 $ret = array();
575                 foreach ((array) $requestData as $k => $v) {
576                         if (is_int($k) && $prefix != null) {
577                                 $k = urlencode($prefix . $k);
578                         } // END - if
579
580                         if ((!empty($key)) || ($key === 0)) {
581                                 $k = $key . '[' . urlencode($k) . ']';
582                         } // END - if
583
584                         if (is_array($v) || is_object($v)) {
585                                 array_push($ret, http_build_query($v, '', $sep, $k));
586                         } else {
587                                 array_push($ret, $k . '=' . urlencode($v));
588                         }
589                 } // END - foreach
590
591                 if (empty($sep)) {
592                         $sep = ini_get('arg_separator.output');
593                 } // END - if
594
595                 return implode($sep, $ret);
596         }
597 } // END - if
598
599 if (!function_exists('http_chunked_decode')) {
600         /**
601          * dechunk an HTTP 'transfer-encoding: chunked' message.
602          *
603          * @param       $chunk          The encoded message
604          * @return      $dechunk        The decoded message. If $chunk wasn't encoded properly debug_report_bug() is being called
605          * @author      Marques Johansson (initial author)
606          * @author      Roland Haeder (heavy modifications and simplification)
607          * @link        http://php.net/manual/en/function.http-chunked-decode.php#89786
608          */
609         function http_chunked_decode ($chunk) {
610                 // Detect multi-byte encoding
611                 $mbPrefix = detectMultiBytePrefix($chunk);
612                 //* DEBUG: */ print 'mbPrefix=' . $mbPrefix . '<br />';
613
614                 // Init some variables
615                 $offset = 0;
616                 $len = call_user_func_array($mbPrefix . 'strlen', array(($chunk)));
617                 $dechunk = '';
618
619                 // Walk through all chunks
620                 while ($offset < $len) {
621                         // Where does the \r\n begin?
622                         $lineEndAt = call_user_func_array($mbPrefix . 'strpos', array($chunk, getConfig('HTTP_EOL'), $offset));
623
624                         /* DEBUG: *
625                         print 'lineEndAt[<em>'.__LINE__.'</em>]='.$lineEndAt.'<br />
626 offset[<em>'.__LINE__.'</em>]='.$offset.'<br />
627 len='.$len.'<br />
628 next[offset,10]=<pre>'.replaceReturnNewLine(htmlentities(call_user_func_array($mbPrefix . 'substr', array($chunk, $offset, 10)))).'</pre>';
629                         /* DEBUG: */
630
631                         // Get next hex-coded chunk length
632                         $chunkLenHex = call_user_func_array($mbPrefix . 'substr', array($chunk, $offset, ($lineEndAt - $offset)));
633
634                         /* DEBUG: *
635                         print 'chunkLenHex[<em>'.__LINE__.'</em>]='.replaceReturnNewLine(htmlentities($chunkLenHex)).'<br />
636 ';
637                         /* DEBUG: */
638
639                         // Validation if it is hexadecimal
640                         if (!isHexadecimal($chunkLenHex)) {
641                                 // Please help debugging this
642                                 //* DEBUG: */ die('ABORT:chunkLenHex=<pre>'.replaceReturnNewLine(htmlentities($chunkLenHex)).'</pre>');
643                                 debug_report_bug(__FUNCTION__, __LINE__, 'Value ' . $chunkLenHex . ' is no valid hexa-decimal string.');
644
645                                 // This won't be reached
646                                 return $chunk;
647                         } // END - if
648
649                         // Position of next chunk is right after \r\n
650                         $offset   = $offset + strlen($chunkLenHex) + strlen(getConfig('HTTP_EOL'));
651                         $chunkLen = hexdec(rtrim($chunkLenHex, getConfig('HTTP_EOL')));
652
653                         /* DEBUG: *
654                         print 'chunkLen='.$chunkLen.'<br />
655 offset[<em>'.__LINE__.'</em>]='.$offset.'<br />';
656                         /* DEBUG: */
657
658                         // Moved out for debugging
659                         $next  = call_user_func_array($mbPrefix . 'substr', array($chunk, $offset, $chunkLen));
660                         //* DEBUG: */ print 'next=<pre>'.replaceReturnNewLine(htmlentities($next)).'</pre>';
661
662                         /*
663                          * Hack for e.g. YaCy HTTPDaemon (Anomic Server), this HTTP server
664                          * is currently (revision 7567 and maybe earlier) broken and does
665                          * not include the \r\n characters when it sents a "chunked" HTTP
666                          * message.
667                          */
668                         $count = 0;
669                         if (isBrokenHttpServerImplentation()) {
670                                 // Count occurrences of \r\n
671                                 $count = call_user_func_array($mbPrefix . 'substr_count', array($next, getConfig('HTTP_EOL')));
672                         } // END - if
673
674                         /*
675                          * Correct chunk length because some broken HTTP server
676                          * implementation subtract occurrences of \r\n in their chunk
677                          * lengths.
678                          */
679                         $chunkLen = hexdec(rtrim($chunkLenHex, getConfig('HTTP_EOL'))) - ($count * strlen(getConfig('HTTP_EOL')));
680
681                         // Add next chunk to $dechunk
682                         $dechunk .= call_user_func_array($mbPrefix . 'substr', array($chunk, $offset, $chunkLen));
683
684                         /* DEBUG: *
685                         print('offset[<em>'.__LINE__.'</em>]='.$offset.'<br />
686 lineEndAt[<em>'.__LINE__.'</em>]='.$lineEndAt.'<br />
687 len='.$len.'<br />
688 count='.$count.'<br />
689 chunkLen='.$chunkLen.'<br />
690 chunkLenHex='.$chunkLenHex.'<br />
691 dechunk=<pre>'.replaceReturnNewLine(htmlentities($dechunk)).'</pre>
692 chunk=<pre>'.replaceReturnNewLine(htmlentities($chunk)).'</pre>');
693                         /* DEBUG: */
694
695                         // Is $offset + $chunkLen larger than or equal $len?
696                         if (($offset + $chunkLen) >= $len) {
697                                 // Then stop processing here
698                                 break;
699                         } // END - if
700
701                         // Calculate offset of next chunk
702                         $offset = call_user_func_array($mbPrefix . 'strpos', array($chunk, getConfig('HTTP_EOL'), $offset + $chunkLen)) + 2;
703
704                         /* DEBUG: *
705                         print('offset[<em>'.__LINE__.'</em>]='.$offset.'<br />
706 next[100]=<pre>'.replaceReturnNewLine(htmlentities(call_user_func_array($mbPrefix . 'substr', array($chunk, $offset, 100)))).'</pre>
707 ---:---:---:---:---:---:---:---:---<br />
708 ');
709                         /* DEBUG: */
710                 } // END - while
711
712                 // Return de-chunked string
713                 return $dechunk;
714         }
715 } // END - if
716
717 // Extract host from script name
718 function extractHostnameFromUrl (&$script) {
719         // Use default SERVER_URL by default... ;) So?
720         $url = getServerUrl();
721
722         // Is this URL valid?
723         if (substr($script, 0, 7) == 'http://') {
724                 // Use the hostname from script URL as new hostname
725                 $url = substr($script, 7);
726                 $extract = explode('/', $url);
727                 $url = $extract[0];
728                 // Done extracting the URL :)
729         } // END - if
730
731         // Extract host name
732         $host = str_replace('http://', '', $url);
733         if (isInString('/', $host)) {
734                 $host = substr($host, 0, strpos($host, '/'));
735         } // END - if
736
737         // Generate relative URL
738         //* DEBUG: */ debugOutput('SCRIPT=' . $script);
739         if (substr(strtolower($script), 0, 7) == 'http://') {
740                 // But only if http:// is in front!
741                 $script = substr($script, (strlen($url) + 7));
742         } elseif (substr(strtolower($script), 0, 8) == 'https://') {
743                 // Does this work?!
744                 $script = substr($script, (strlen($url) + 8));
745         }
746
747         //* DEBUG: */ debugOutput('SCRIPT=' . $script);
748         if (substr($script, 0, 1) == '/') {
749                 $script = substr($script, 1);
750         } // END - if
751
752         // Return host name
753         return $host;
754 }
755
756 // [EOF]
757 ?>