Use Guzzle
This commit is contained in:
parent
d4ae6c67db
commit
e85d47dfd4
|
@ -17,7 +17,19 @@ class UrlHelper {
|
||||||
static string $fetch_last_modified;
|
static string $fetch_last_modified;
|
||||||
static string $fetch_effective_url;
|
static string $fetch_effective_url;
|
||||||
static string $fetch_effective_ip_addr;
|
static string $fetch_effective_ip_addr;
|
||||||
static bool $fetch_curl_used;
|
|
||||||
|
private static ?GuzzleHttp\ClientInterface $client = null;
|
||||||
|
|
||||||
|
private static function get_client(): GuzzleHttp\ClientInterface {
|
||||||
|
if (self::$client == null) {
|
||||||
|
self::$client = new GuzzleHttp\Client([
|
||||||
|
GuzzleHttp\RequestOptions::COOKIES => false,
|
||||||
|
GuzzleHttp\RequestOptions::PROXY => Config::get(Config::HTTP_PROXY) ?: null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$client;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, string|int> $parts
|
* @param array<string, string|int> $parts
|
||||||
|
@ -184,57 +196,32 @@ class UrlHelper {
|
||||||
/**
|
/**
|
||||||
* @return false|string
|
* @return false|string
|
||||||
*/
|
*/
|
||||||
static function resolve_redirects(string $url, int $timeout, int $nest = 0) {
|
static function resolve_redirects(string $url, int $timeout) {
|
||||||
$span = Tracer::start(__METHOD__);
|
$span = Tracer::start(__METHOD__);
|
||||||
$span->setAttribute('func.args', json_encode(func_get_args()));
|
$span->setAttribute('func.args', json_encode(func_get_args()));
|
||||||
|
$client = self::get_client();
|
||||||
|
|
||||||
// too many redirects
|
try {
|
||||||
if ($nest > 10) {
|
$response = $client->request('HEAD', $url, [
|
||||||
|
GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => $timeout ?: Config::get(Config::FILE_FETCH_CONNECT_TIMEOUT),
|
||||||
|
GuzzleHttp\RequestOptions::TIMEOUT => $timeout ?: Config::get(Config::FILE_FETCH_TIMEOUT),
|
||||||
|
GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => ['max' => 10, 'track_redirects' => true, 'http_errors' => false],
|
||||||
|
GuzzleHttp\RequestOptions::HEADERS => [
|
||||||
|
'User-Agent' => Config::get_user_agent(),
|
||||||
|
'Connection' => 'close',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
} catch (GuzzleHttp\Exception\GuzzleException $ex) {
|
||||||
|
// TODO: catch just the "too many redirects" exception, and set a different 'error' for general issues
|
||||||
$span->setAttribute('error', 'too many redirects');
|
$span->setAttribute('error', 'too many redirects');
|
||||||
$span->end();
|
$span->end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$context_options = array(
|
// If a history header value doesn't exist there was no redirection and the original URL is fine.
|
||||||
'http' => array(
|
$history_header = $response->getHeader(GuzzleHttp\RedirectMiddleware::HISTORY_HEADER);
|
||||||
'header' => array(
|
|
||||||
'Connection: close'
|
|
||||||
),
|
|
||||||
'method' => 'HEAD',
|
|
||||||
'timeout' => $timeout,
|
|
||||||
'protocol_version'=> 1.1)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Config::get(Config::HTTP_PROXY)) {
|
|
||||||
$context_options['http']['request_fulluri'] = true;
|
|
||||||
$context_options['http']['proxy'] = Config::get(Config::HTTP_PROXY);
|
|
||||||
}
|
|
||||||
|
|
||||||
$context = stream_context_create($context_options);
|
|
||||||
|
|
||||||
// PHP 8 changed the second param from int to bool, but we still support PHP >= 7.4.0
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
$headers = get_headers($url, 0, $context);
|
|
||||||
|
|
||||||
if (is_array($headers)) {
|
|
||||||
$headers = array_reverse($headers); // last one is the correct one
|
|
||||||
|
|
||||||
foreach($headers as $header) {
|
|
||||||
if (stripos($header, 'Location:') === 0) {
|
|
||||||
$url = self::rewrite_relative($url, trim(substr($header, strlen('Location:'))));
|
|
||||||
|
|
||||||
return self::resolve_redirects($url, $timeout, $nest + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$span->end();
|
|
||||||
return $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
$span->setAttribute('error', 'request failed');
|
|
||||||
$span->end();
|
$span->end();
|
||||||
// request failed?
|
return ($history_header ? end($history_header) : $url);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,13 +231,14 @@ class UrlHelper {
|
||||||
// TODO: max_size currently only works for CURL transfers
|
// TODO: max_size currently only works for CURL transfers
|
||||||
// TODO: multiple-argument way is deprecated, first parameter is a hash now
|
// TODO: multiple-argument way is deprecated, first parameter is a hash now
|
||||||
public static function fetch($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
|
public static function fetch($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false,
|
||||||
4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) {
|
4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false, 8: $retry_once_request = false */) {
|
||||||
|
$span = Tracer::start(__METHOD__);
|
||||||
|
$span->setAttribute('func.args', json_encode(func_get_args()));
|
||||||
|
|
||||||
self::$fetch_last_error = "";
|
self::$fetch_last_error = "";
|
||||||
self::$fetch_last_error_code = -1;
|
self::$fetch_last_error_code = -1;
|
||||||
self::$fetch_last_error_content = "";
|
self::$fetch_last_error_content = "";
|
||||||
self::$fetch_last_content_type = "";
|
self::$fetch_last_content_type = "";
|
||||||
self::$fetch_curl_used = false;
|
|
||||||
self::$fetch_last_modified = "";
|
self::$fetch_last_modified = "";
|
||||||
self::$fetch_effective_url = "";
|
self::$fetch_effective_url = "";
|
||||||
self::$fetch_effective_ip_addr = "";
|
self::$fetch_effective_ip_addr = "";
|
||||||
|
@ -258,7 +246,7 @@ class UrlHelper {
|
||||||
if (!is_array($options)) {
|
if (!is_array($options)) {
|
||||||
|
|
||||||
// falling back on compatibility shim
|
// falling back on compatibility shim
|
||||||
$option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent" ];
|
$option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent", "retry-once-request" ];
|
||||||
$tmp = [];
|
$tmp = [];
|
||||||
|
|
||||||
for ($i = 0; $i < func_num_args(); $i++) {
|
for ($i = 0; $i < func_num_args(); $i++) {
|
||||||
|
@ -275,14 +263,12 @@ class UrlHelper {
|
||||||
"post_query" => @func_get_arg(4),
|
"post_query" => @func_get_arg(4),
|
||||||
"timeout" => @func_get_arg(5),
|
"timeout" => @func_get_arg(5),
|
||||||
"timestamp" => @func_get_arg(6),
|
"timestamp" => @func_get_arg(6),
|
||||||
"useragent" => @func_get_arg(7)
|
"useragent" => @func_get_arg(7),
|
||||||
|
"retry-once-request" => @func_get_arg(8),
|
||||||
); */
|
); */
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $options["url"];
|
$url = $options["url"];
|
||||||
|
|
||||||
$span = Tracer::start(__METHOD__);
|
|
||||||
$span->setAttribute('func.args', json_encode(func_get_args()));
|
|
||||||
|
|
||||||
$type = isset($options["type"]) ? $options["type"] : false;
|
$type = isset($options["type"]) ? $options["type"] : false;
|
||||||
$login = isset($options["login"]) ? $options["login"] : false;
|
$login = isset($options["login"]) ? $options["login"] : false;
|
||||||
$pass = isset($options["pass"]) ? $options["pass"] : false;
|
$pass = isset($options["pass"]) ? $options["pass"] : false;
|
||||||
|
@ -303,8 +289,7 @@ class UrlHelper {
|
||||||
$url = self::validate($url, true);
|
$url = self::validate($url, true);
|
||||||
|
|
||||||
if (!$url) {
|
if (!$url) {
|
||||||
self::$fetch_last_error = "Requested URL failed extended validation.";
|
self::$fetch_last_error = 'Requested URL failed extended validation.';
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
$span->end();
|
$span->end();
|
||||||
return false;
|
return false;
|
||||||
|
@ -313,319 +298,146 @@ class UrlHelper {
|
||||||
$url_host = parse_url($url, PHP_URL_HOST);
|
$url_host = parse_url($url, PHP_URL_HOST);
|
||||||
$ip_addr = gethostbyname($url_host);
|
$ip_addr = gethostbyname($url_host);
|
||||||
|
|
||||||
if (!$ip_addr || strpos($ip_addr, "127.") === 0) {
|
if (!$ip_addr || strpos($ip_addr, '127.') === 0) {
|
||||||
self::$fetch_last_error = "URL hostname failed to resolve or resolved to a loopback address ($ip_addr)";
|
self::$fetch_last_error = "URL hostname failed to resolve or resolved to a loopback address ($ip_addr)";
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
$span->end();
|
$span->end();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (function_exists('curl_init') && !ini_get("open_basedir")) {
|
$req_options = [
|
||||||
|
GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => $timeout ?: Config::get(Config::FILE_FETCH_CONNECT_TIMEOUT),
|
||||||
|
GuzzleHttp\RequestOptions::TIMEOUT => $timeout ?: Config::get(Config::FILE_FETCH_TIMEOUT),
|
||||||
|
GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => $followlocation ? ['max' => 20, 'track_redirects' => true] : false,
|
||||||
|
GuzzleHttp\RequestOptions::HEADERS => [
|
||||||
|
'User-Agent' => $useragent ?: Config::get_user_agent(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
self::$fetch_curl_used = true;
|
if ($last_modified && !$post_query)
|
||||||
|
$req_options[GuzzleHttp\RequestOptions::HEADERS]['If-Modified-Since'] = $last_modified;
|
||||||
|
|
||||||
$ch = curl_init($url);
|
if ($http_accept)
|
||||||
|
$req_options[GuzzleHttp\RequestOptions::HEADERS]['Accept'] = $http_accept;
|
||||||
|
|
||||||
if (!$ch) {
|
if ($http_referrer)
|
||||||
self::$fetch_last_error = "curl_init() failed";
|
$req_options[GuzzleHttp\RequestOptions::HEADERS]['Referer'] = $http_referrer;
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
if ($login && $pass)
|
||||||
return false;
|
$req_options[GuzzleHttp\RequestOptions::AUTH] = [$login, $pass];
|
||||||
|
|
||||||
|
if ($post_query)
|
||||||
|
$req_options[GuzzleHttp\RequestOptions::FORM_PARAMS] = $post_query;
|
||||||
|
|
||||||
|
if ($max_size) {
|
||||||
|
$req_options[GuzzleHttp\RequestOptions::PROGRESS] = function($download_size, $downloaded, $upload_size, $uploaded) use(&$max_size, $url) {
|
||||||
|
//Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
|
||||||
|
|
||||||
|
if ($downloaded > $max_size) {
|
||||||
|
Debug::log("[UrlHelper] fetch error: max size of $max_size bytes exceeded when downloading $url . Aborting.", Debug::LOG_VERBOSE);
|
||||||
|
throw new \LengthException("Download exceeded size limit");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
# Alternative/supplement to `progress` checking
|
||||||
|
$req_options[GuzzleHttp\RequestOptions::ON_HEADERS] = function(Psr\Http\Message\ResponseInterface $response) use(&$max_size, $url) {
|
||||||
|
$content_length = $response->getHeaderLine('Content-Length');
|
||||||
|
if ($content_length > $max_size) {
|
||||||
|
Debug::log("[UrlHelper] fetch error: server indicated (via 'Content-Length: {$content_length}') max size of $max_size bytes " .
|
||||||
|
"would be exceeded when downloading $url . Aborting.", Debug::LOG_VERBOSE);
|
||||||
|
throw new \LengthException("Server sent 'Content-Length' exceeding download limit");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = self::get_client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (($options['retry-once-request'] ?? null) instanceof Psr\Http\Message\RequestInterface) {
|
||||||
|
$response = $client->send($options['retry-once-request']);
|
||||||
|
} else {
|
||||||
|
$response = $client->request($post_query ? 'POST' : 'GET', $url, $req_options);
|
||||||
}
|
}
|
||||||
|
} catch (\LengthException $ex) {
|
||||||
|
// 'Content-Length' exceeded the download limit
|
||||||
|
self::$fetch_last_error = (string) $ex;
|
||||||
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
|
$span->end();
|
||||||
|
return false;
|
||||||
|
} catch (GuzzleHttp\Exception\GuzzleException $ex) {
|
||||||
|
self::$fetch_last_error = (string) $ex;
|
||||||
|
|
||||||
$curl_http_headers = [];
|
if ($ex instanceof GuzzleHttp\Exception\RequestException) {
|
||||||
|
if ($ex instanceof GuzzleHttp\Exception\BadResponseException) {
|
||||||
|
// 4xx or 5xx
|
||||||
|
self::$fetch_last_error_code = $ex->getResponse()->getStatusCode();
|
||||||
|
|
||||||
if ($last_modified && !$post_query)
|
# TODO: Retry with CURLAUTH_ANY if the response code is 403? Was this actually an issue before?
|
||||||
array_push($curl_http_headers, "If-Modified-Since: $last_modified");
|
# https://docs.guzzlephp.org/en/stable/faq.html#how-can-i-add-custom-curl-options
|
||||||
|
// if (self::$fetch_last_error_code === 403) {}
|
||||||
|
|
||||||
if ($http_accept)
|
self::$fetch_last_content_type = $ex->getResponse()->getHeaderLine('content-type');
|
||||||
array_push($curl_http_headers, "Accept: " . $http_accept);
|
|
||||||
|
|
||||||
if (count($curl_http_headers) > 0)
|
if ($type && strpos(self::$fetch_last_content_type, "$type") === false)
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_http_headers);
|
self::$fetch_last_error_content = (string) $ex->getResponse()->getBody();
|
||||||
|
} elseif (array_key_exists('errno', $ex->getHandlerContext())) {
|
||||||
|
$errno = (int) $ex->getHandlerContext()['errno'];
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : Config::get(Config::FILE_FETCH_CONNECT_TIMEOUT));
|
// By default, all supported encoding types are sent via `Accept-Encoding` and decoding of
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout ? $timeout : Config::get(Config::FILE_FETCH_TIMEOUT));
|
// responses with `Content-Encoding` is automatically attempted. If this fails, we do a
|
||||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $followlocation);
|
// single retry with `Accept-Encoding: none` to try and force an unencoded response.
|
||||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
|
if (($errno === \CURLE_WRITE_ERROR || $errno === \CURLE_BAD_CONTENT_ENCODING) &&
|
||||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
|
!array_key_exists('retry-once-request', $options)) {
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
$options['retry-once-request'] = $ex->getRequest()->withHeader('Accept-Encoding', 'none');
|
||||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
return self::fetch($options);
|
||||||
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent : Config::get_user_agent());
|
|
||||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
|
||||||
curl_setopt($ch, CURLOPT_COOKIEJAR, "/dev/null");
|
|
||||||
|
|
||||||
if ($http_referrer)
|
|
||||||
curl_setopt($ch, CURLOPT_REFERER, $http_referrer);
|
|
||||||
|
|
||||||
if ($max_size) {
|
|
||||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
|
||||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 16384); // needed to get 5 arguments in progress function?
|
|
||||||
|
|
||||||
// holy shit closures in php
|
|
||||||
// download & upload are *expected* sizes respectively, could be zero
|
|
||||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($curl_handle, $download_size, $downloaded, $upload_size, $uploaded) use(&$max_size, $url) {
|
|
||||||
//Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
|
|
||||||
|
|
||||||
if ($downloaded > $max_size) {
|
|
||||||
Debug::log("[UrlHelper] fetch error: curl reached max size of $max_size bytes downloading $url, aborting.", Debug::LOG_VERBOSE);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config::get(Config::HTTP_PROXY)) {
|
|
||||||
curl_setopt($ch, CURLOPT_PROXY, Config::get(Config::HTTP_PROXY));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($post_query) {
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($login && $pass)
|
|
||||||
curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
|
|
||||||
|
|
||||||
$ret = @curl_exec($ch);
|
|
||||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
|
|
||||||
// CURLAUTH_BASIC didn't work, let's retry with CURLAUTH_ANY in case it's actually something
|
|
||||||
// unusual like NTLM...
|
|
||||||
if ($http_code == 403 && $login && $pass) {
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
|
||||||
|
|
||||||
$ret = @curl_exec($ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
|
|
||||||
curl_setopt($ch, CURLOPT_ENCODING, 'none');
|
|
||||||
$ret = @curl_exec($ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
$headers_length = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
|
||||||
$headers = explode("\r\n", substr($ret, 0, $headers_length));
|
|
||||||
$contents = substr($ret, $headers_length);
|
|
||||||
|
|
||||||
foreach ($headers as $header) {
|
|
||||||
if (strstr($header, ": ") !== false) {
|
|
||||||
list ($key, $value) = explode(": ", $header);
|
|
||||||
|
|
||||||
if (strtolower($key) == "last-modified") {
|
|
||||||
self::$fetch_last_modified = $value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (substr(strtolower($header), 0, 7) == 'http/1.') {
|
|
||||||
self::$fetch_last_error_code = (int) substr($header, 9, 3);
|
|
||||||
self::$fetch_last_error = $header;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
self::$fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
|
||||||
|
|
||||||
self::$fetch_effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
|
||||||
|
|
||||||
if (!self::validate(self::$fetch_effective_url, true)) {
|
|
||||||
self::$fetch_last_error = "URL received after redirection failed extended validation.";
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$fetch_effective_ip_addr = gethostbyname(parse_url(self::$fetch_effective_url, PHP_URL_HOST));
|
|
||||||
|
|
||||||
if (!self::$fetch_effective_ip_addr || strpos(self::$fetch_effective_ip_addr, "127.") === 0) {
|
|
||||||
self::$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address (".self::$fetch_effective_ip_addr.")";
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$fetch_last_error_code = $http_code;
|
|
||||||
|
|
||||||
if ($http_code != 200 || $type && strpos(self::$fetch_last_content_type, "$type") === false) {
|
|
||||||
|
|
||||||
if (curl_errno($ch) != 0) {
|
|
||||||
self::$fetch_last_error .= "; " . curl_errno($ch) . " " . curl_error($ch);
|
|
||||||
} else {
|
|
||||||
self::$fetch_last_error = "HTTP Code: $http_code ";
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$fetch_last_error_content = $contents;
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$contents) {
|
|
||||||
if (curl_errno($ch) === 0) {
|
|
||||||
self::$fetch_last_error = 'Successful response, but no content was received.';
|
|
||||||
} else {
|
|
||||||
self::$fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
|
|
||||||
}
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
$is_gzipped = RSSUtils::is_gzipped($contents);
|
|
||||||
|
|
||||||
if ($is_gzipped && is_string($contents)) {
|
|
||||||
$tmp = @gzdecode($contents);
|
|
||||||
|
|
||||||
if ($tmp) $contents = $tmp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
$span->end();
|
$span->end();
|
||||||
|
|
||||||
return $contents;
|
return false;
|
||||||
} else {
|
|
||||||
|
|
||||||
self::$fetch_curl_used = false;
|
|
||||||
|
|
||||||
if ($login && $pass){
|
|
||||||
$url_parts = array();
|
|
||||||
|
|
||||||
preg_match("/(^[^:]*):\/\/(.*)/", $url, $url_parts);
|
|
||||||
|
|
||||||
$pass = urlencode($pass);
|
|
||||||
|
|
||||||
if ($url_parts[1] && $url_parts[2]) {
|
|
||||||
$url = $url_parts[1] . "://$login:$pass@" . $url_parts[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should this support POST requests or not? idk
|
|
||||||
|
|
||||||
$context_options = array(
|
|
||||||
'http' => array(
|
|
||||||
'header' => array(
|
|
||||||
'Connection: close'
|
|
||||||
),
|
|
||||||
'method' => 'GET',
|
|
||||||
'ignore_errors' => true,
|
|
||||||
'timeout' => $timeout ? $timeout : Config::get(Config::FILE_FETCH_TIMEOUT),
|
|
||||||
'protocol_version'=> 1.1)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$post_query && $last_modified)
|
|
||||||
array_push($context_options['http']['header'], "If-Modified-Since: $last_modified");
|
|
||||||
|
|
||||||
if ($http_accept)
|
|
||||||
array_push($context_options['http']['header'], "Accept: $http_accept");
|
|
||||||
|
|
||||||
if ($http_referrer)
|
|
||||||
array_push($context_options['http']['header'], "Referer: $http_referrer");
|
|
||||||
|
|
||||||
if (Config::get(Config::HTTP_PROXY)) {
|
|
||||||
$context_options['http']['request_fulluri'] = true;
|
|
||||||
$context_options['http']['proxy'] = Config::get(Config::HTTP_PROXY);
|
|
||||||
}
|
|
||||||
|
|
||||||
$context = stream_context_create($context_options);
|
|
||||||
|
|
||||||
$old_error = error_get_last();
|
|
||||||
|
|
||||||
self::$fetch_effective_url = self::resolve_redirects($url, $timeout ? $timeout : Config::get(Config::FILE_FETCH_CONNECT_TIMEOUT));
|
|
||||||
|
|
||||||
if (!self::validate(self::$fetch_effective_url, true)) {
|
|
||||||
self::$fetch_last_error = "URL received after redirection failed extended validation.";
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$fetch_effective_ip_addr = gethostbyname(parse_url(self::$fetch_effective_url, PHP_URL_HOST));
|
|
||||||
|
|
||||||
if (!self::$fetch_effective_ip_addr || strpos(self::$fetch_effective_ip_addr, "127.") === 0) {
|
|
||||||
self::$fetch_last_error = "URL hostname received after redirection failed to resolve or resolved to a loopback address (".self::$fetch_effective_ip_addr.")";
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = @file_get_contents($url, false, $context);
|
|
||||||
|
|
||||||
if ($data === false) {
|
|
||||||
self::$fetch_last_error = "'file_get_contents' failed.";
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($http_response_header as $header) {
|
|
||||||
if (strstr($header, ": ") !== false) {
|
|
||||||
list ($key, $value) = explode(": ", $header);
|
|
||||||
|
|
||||||
$key = strtolower($key);
|
|
||||||
|
|
||||||
if ($key == 'content-type') {
|
|
||||||
self::$fetch_last_content_type = $value;
|
|
||||||
// don't abort here b/c there might be more than one
|
|
||||||
// e.g. if we were being redirected -- last one is the right one
|
|
||||||
} else if ($key == 'last-modified') {
|
|
||||||
self::$fetch_last_modified = $value;
|
|
||||||
} else if ($key == 'location') {
|
|
||||||
self::$fetch_effective_url = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (substr(strtolower($header), 0, 7) == 'http/1.') {
|
|
||||||
self::$fetch_last_error_code = (int) substr($header, 9, 3);
|
|
||||||
self::$fetch_last_error = $header;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$fetch_last_error_code != 200) {
|
|
||||||
$error = error_get_last();
|
|
||||||
|
|
||||||
if (($error['message'] ?? '') != ($old_error['message'] ?? '')) {
|
|
||||||
self::$fetch_last_error .= "; " . $error["message"];
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$fetch_last_error_content = $data;
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data) {
|
|
||||||
$is_gzipped = RSSUtils::is_gzipped($data);
|
|
||||||
|
|
||||||
if ($is_gzipped) {
|
|
||||||
$tmp = @gzdecode($data);
|
|
||||||
|
|
||||||
if ($tmp) $data = $tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
$span->end();
|
|
||||||
return $data;
|
|
||||||
} else {
|
|
||||||
self::$fetch_last_error = 'Successful response, but no content was received.';
|
|
||||||
|
|
||||||
$span->setAttribute('error', self::$fetch_last_error);
|
|
||||||
$span->end();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep setting expected 'fetch_last_error_code' and 'fetch_last_error' values
|
||||||
|
self::$fetch_last_error_code = $response->getStatusCode();
|
||||||
|
self::$fetch_last_error = "HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}";
|
||||||
|
self::$fetch_last_modified = $response->getHeaderLine('last-modified');
|
||||||
|
self::$fetch_last_content_type = $response->getHeaderLine('content-type');
|
||||||
|
|
||||||
|
// If a history header value doesn't exist there was no redirection and the original URL is fine.
|
||||||
|
$history_header = $response->getHeader(GuzzleHttp\RedirectMiddleware::HISTORY_HEADER);
|
||||||
|
self::$fetch_effective_url = $history_header ? end($history_header) : $url;
|
||||||
|
|
||||||
|
if (!self::validate(self::$fetch_effective_url, true)) {
|
||||||
|
self::$fetch_last_error = "URL received after redirection failed extended validation.";
|
||||||
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
|
$span->end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$fetch_effective_ip_addr = gethostbyname(parse_url(self::$fetch_effective_url, PHP_URL_HOST));
|
||||||
|
|
||||||
|
if (!self::$fetch_effective_ip_addr || strpos(self::$fetch_effective_ip_addr, '127.') === 0) {
|
||||||
|
self::$fetch_last_error = 'URL hostname received after redirection failed to resolve or resolved to a loopback address (' .
|
||||||
|
self::$fetch_effective_ip_addr . ')';
|
||||||
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
|
$span->end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = (string) $response->getBody();
|
||||||
|
|
||||||
|
if (!$body) {
|
||||||
|
self::$fetch_last_error = 'Successful response, but no content was received.';
|
||||||
|
$span->setAttribute('error', self::$fetch_last_error);
|
||||||
|
$span->end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$span->end();
|
||||||
|
return $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
"j4mie/idiorm": "dev-master",
|
"j4mie/idiorm": "dev-master",
|
||||||
"open-telemetry/exporter-otlp": "^1.0",
|
"open-telemetry/exporter-otlp": "^1.0",
|
||||||
"php-http/guzzle7-adapter": "^1.0",
|
"php-http/guzzle7-adapter": "^1.0",
|
||||||
"soundasleep/html2text": "^2.1"
|
"soundasleep/html2text": "^2.1",
|
||||||
|
"guzzlehttp/guzzle": "^7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "1.10.3",
|
"phpstan/phpstan": "1.10.3",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "d65a2e896d59d3d603fd6cda0db3b646",
|
"content-hash": "a0465ea624d9e79bc5d8b04a345b1ad6",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "beberlei/assert",
|
"name": "beberlei/assert",
|
||||||
|
@ -261,16 +261,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/guzzle",
|
"name": "guzzlehttp/guzzle",
|
||||||
"version": "7.8.0",
|
"version": "7.8.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/guzzle.git",
|
"url": "https://github.com/guzzle/guzzle.git",
|
||||||
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9"
|
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9",
|
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
|
||||||
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9",
|
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -285,11 +285,11 @@
|
||||||
"psr/http-client-implementation": "1.0"
|
"psr/http-client-implementation": "1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
||||||
"php-http/message-factory": "^1.1",
|
"php-http/message-factory": "^1.1",
|
||||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
|
"phpunit/phpunit": "^8.5.36 || ^9.6.15",
|
||||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
@ -367,7 +367,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||||
"source": "https://github.com/guzzle/guzzle/tree/7.8.0"
|
"source": "https://github.com/guzzle/guzzle/tree/7.8.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -383,7 +383,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-08-27T10:20:53+00:00"
|
"time": "2023-12-03T20:35:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/promises",
|
"name": "guzzlehttp/promises",
|
||||||
|
@ -4405,5 +4405,5 @@
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": [],
|
"platform-dev": [],
|
||||||
"plugin-api-version": "2.3.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,17 +340,17 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/guzzle",
|
"name": "guzzlehttp/guzzle",
|
||||||
"version": "7.8.0",
|
"version": "7.8.1",
|
||||||
"version_normalized": "7.8.0.0",
|
"version_normalized": "7.8.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/guzzle.git",
|
"url": "https://github.com/guzzle/guzzle.git",
|
||||||
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9"
|
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9",
|
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
|
||||||
"reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9",
|
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -365,11 +365,11 @@
|
||||||
"psr/http-client-implementation": "1.0"
|
"psr/http-client-implementation": "1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
||||||
"php-http/message-factory": "^1.1",
|
"php-http/message-factory": "^1.1",
|
||||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
|
"phpunit/phpunit": "^8.5.36 || ^9.6.15",
|
||||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
@ -377,7 +377,7 @@
|
||||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||||
"psr/log": "Required for using the Log middleware"
|
"psr/log": "Required for using the Log middleware"
|
||||||
},
|
},
|
||||||
"time": "2023-08-27T10:20:53+00:00",
|
"time": "2023-12-03T20:35:24+00:00",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"bamarni-bin": {
|
"bamarni-bin": {
|
||||||
|
@ -449,7 +449,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||||
"source": "https://github.com/guzzle/guzzle/tree/7.8.0"
|
"source": "https://github.com/guzzle/guzzle/tree/7.8.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
'name' => '__root__',
|
'name' => '__root__',
|
||||||
'pretty_version' => 'dev-master',
|
'pretty_version' => 'dev-master',
|
||||||
'version' => 'dev-master',
|
'version' => 'dev-master',
|
||||||
'reference' => '2b8e34453234b8b31ebc9e7020f8677bf3889898',
|
'reference' => 'd4ae6c67db8c966ab4998fda6df14072b103106b',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
'__root__' => array(
|
'__root__' => array(
|
||||||
'pretty_version' => 'dev-master',
|
'pretty_version' => 'dev-master',
|
||||||
'version' => 'dev-master',
|
'version' => 'dev-master',
|
||||||
'reference' => '2b8e34453234b8b31ebc9e7020f8677bf3889898',
|
'reference' => 'd4ae6c67db8c966ab4998fda6df14072b103106b',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
@ -65,9 +65,9 @@
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
'guzzlehttp/guzzle' => array(
|
'guzzlehttp/guzzle' => array(
|
||||||
'pretty_version' => '7.8.0',
|
'pretty_version' => '7.8.1',
|
||||||
'version' => '7.8.0.0',
|
'version' => '7.8.1.0',
|
||||||
'reference' => '1110f66a6530a40fe7aea0378fe608ee2b2248f9',
|
'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
|
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
@ -371,8 +371,8 @@
|
||||||
'psr/http-client-implementation' => array(
|
'psr/http-client-implementation' => array(
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
'provided' => array(
|
'provided' => array(
|
||||||
0 => '1.0',
|
0 => '*',
|
||||||
1 => '*',
|
1 => '1.0',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'psr/http-factory' => array(
|
'psr/http-factory' => array(
|
||||||
|
|
|
@ -3,6 +3,14 @@
|
||||||
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
|
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
|
||||||
|
|
||||||
|
|
||||||
|
## 7.8.1 - 2023-12-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated links in docs to their canonical versions
|
||||||
|
- Replaced `call_user_func*` with native calls
|
||||||
|
|
||||||
|
|
||||||
## 7.8.0 - 2023-08-27
|
## 7.8.0 - 2023-08-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -643,7 +651,8 @@ object).
|
||||||
* Note: This has been changed in 5.0.3 to now encode query string values by
|
* Note: This has been changed in 5.0.3 to now encode query string values by
|
||||||
default unless the `rawString` argument is provided when setting the query
|
default unless the `rawString` argument is provided when setting the query
|
||||||
string on a URL: Now allowing many more characters to be present in the
|
string on a URL: Now allowing many more characters to be present in the
|
||||||
query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A
|
query string without being percent encoded. See
|
||||||
|
https://datatracker.ietf.org/doc/html/rfc3986#appendix-A
|
||||||
|
|
||||||
|
|
||||||
## 5.0.1 - 2014-10-16
|
## 5.0.1 - 2014-10-16
|
||||||
|
@ -1182,7 +1191,7 @@ interfaces.
|
||||||
|
|
||||||
## 3.4.0 - 2013-04-11
|
## 3.4.0 - 2013-04-11
|
||||||
|
|
||||||
* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289
|
* Bug fix: URLs are now resolved correctly based on https://datatracker.ietf.org/doc/html/rfc3986#section-5.2. #289
|
||||||
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
|
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
|
||||||
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
|
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
|
||||||
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
|
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Guzzle, PHP HTTP client
|
# Guzzle, PHP HTTP client
|
||||||
|
|
||||||
[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
|
[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
|
||||||
[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
|
[![Build Status](https://img.shields.io/github/actions/workflow/status/guzzle/guzzle/ci.yml?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
|
||||||
[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)
|
[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)
|
||||||
|
|
||||||
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
|
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
|
||||||
|
@ -66,7 +66,7 @@ composer require guzzlehttp/guzzle
|
||||||
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
|
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
|
||||||
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
|
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
|
||||||
| 6.x | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
|
| 6.x | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
|
||||||
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.3 |
|
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.4 |
|
||||||
|
|
||||||
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
|
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
|
||||||
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
|
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
|
||||||
|
|
|
@ -189,11 +189,11 @@ $client = new GuzzleHttp\Client(['handler' => $handler]);
|
||||||
|
|
||||||
## POST Requests
|
## POST Requests
|
||||||
|
|
||||||
This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
|
This version added the [`form_params`](https://docs.guzzlephp.org/en/latest/request-options.html#form_params)
|
||||||
and `multipart` request options. `form_params` is an associative array of
|
and `multipart` request options. `form_params` is an associative array of
|
||||||
strings or array of strings and is used to serialize an
|
strings or array of strings and is used to serialize an
|
||||||
`application/x-www-form-urlencoded` POST request. The
|
`application/x-www-form-urlencoded` POST request. The
|
||||||
[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
|
[`multipart`](https://docs.guzzlephp.org/en/latest/request-options.html#multipart)
|
||||||
option is now used to send a multipart/form-data POST request.
|
option is now used to send a multipart/form-data POST request.
|
||||||
|
|
||||||
`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
|
`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
|
||||||
|
@ -209,7 +209,7 @@ The `base_url` option has been renamed to `base_uri`.
|
||||||
|
|
||||||
## Rewritten Adapter Layer
|
## Rewritten Adapter Layer
|
||||||
|
|
||||||
Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
|
Guzzle now uses [RingPHP](https://ringphp.readthedocs.org/en/latest) to send
|
||||||
HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
|
HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
|
||||||
is still supported, but it has now been renamed to `handler`. Instead of
|
is still supported, but it has now been renamed to `handler`. Instead of
|
||||||
passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
|
passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
|
||||||
|
@ -575,7 +575,7 @@ You can intercept a request and inject a response using the `intercept()` event
|
||||||
of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
|
of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
|
||||||
`GuzzleHttp\Event\ErrorEvent` event.
|
`GuzzleHttp\Event\ErrorEvent` event.
|
||||||
|
|
||||||
See: http://docs.guzzlephp.org/en/latest/events.html
|
See: https://docs.guzzlephp.org/en/latest/events.html
|
||||||
|
|
||||||
## Inflection
|
## Inflection
|
||||||
|
|
||||||
|
@ -668,9 +668,9 @@ in separate repositories:
|
||||||
|
|
||||||
The service description layer of Guzzle has moved into two separate packages:
|
The service description layer of Guzzle has moved into two separate packages:
|
||||||
|
|
||||||
- http://github.com/guzzle/command Provides a high level abstraction over web
|
- https://github.com/guzzle/command Provides a high level abstraction over web
|
||||||
services by representing web service operations using commands.
|
services by representing web service operations using commands.
|
||||||
- http://github.com/guzzle/guzzle-services Provides an implementation of
|
- https://github.com/guzzle/guzzle-services Provides an implementation of
|
||||||
guzzle/command that provides request serialization and response parsing using
|
guzzle/command that provides request serialization and response parsing using
|
||||||
Guzzle service descriptions.
|
Guzzle service descriptions.
|
||||||
|
|
||||||
|
@ -870,7 +870,7 @@ HeaderInterface (e.g. toArray(), getAll(), etc.).
|
||||||
3.3 to 3.4
|
3.3 to 3.4
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
|
Base URLs of a client now follow the rules of https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2 when merging URLs.
|
||||||
|
|
||||||
3.2 to 3.3
|
3.2 to 3.3
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -63,10 +63,10 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"bamarni/composer-bin-plugin": "^1.8.1",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
|
||||||
"php-http/message-factory": "^1.1",
|
"php-http/message-factory": "^1.1",
|
||||||
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
|
"phpunit/phpunit": "^8.5.36 || ^9.6.15",
|
||||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
|
|
@ -243,7 +243,7 @@ class CookieJar implements CookieJarInterface
|
||||||
/**
|
/**
|
||||||
* Computes cookie path following RFC 6265 section 5.1.4
|
* Computes cookie path following RFC 6265 section 5.1.4
|
||||||
*
|
*
|
||||||
* @see https://tools.ietf.org/html/rfc6265#section-5.1.4
|
* @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
|
||||||
*/
|
*/
|
||||||
private function getCookiePathFromRequest(RequestInterface $request): string
|
private function getCookiePathFromRequest(RequestInterface $request): string
|
||||||
{
|
{
|
||||||
|
|
|
@ -420,7 +420,7 @@ class SetCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the leading '.' as per spec in RFC 6265.
|
// Remove the leading '.' as per spec in RFC 6265.
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.3
|
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
|
||||||
$cookieDomain = \ltrim(\strtolower($cookieDomain), '.');
|
$cookieDomain = \ltrim(\strtolower($cookieDomain), '.');
|
||||||
|
|
||||||
$domain = \strtolower($domain);
|
$domain = \strtolower($domain);
|
||||||
|
@ -431,7 +431,7 @@ class SetCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matching the subdomain according to RFC 6265.
|
// Matching the subdomain according to RFC 6265.
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.1.3
|
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
|
||||||
if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
|
if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,7 +256,7 @@ class CurlFactory implements CurlFactoryInterface
|
||||||
|
|
||||||
$method = $easy->request->getMethod();
|
$method = $easy->request->getMethod();
|
||||||
if ($method === 'PUT' || $method === 'POST') {
|
if ($method === 'PUT' || $method === 'POST') {
|
||||||
// See https://tools.ietf.org/html/rfc7230#section-3.3.2
|
// See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
|
||||||
if (!$easy->request->hasHeader('Content-Length')) {
|
if (!$easy->request->hasHeader('Content-Length')) {
|
||||||
$conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
|
$conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@ namespace GuzzleHttp;
|
||||||
/**
|
/**
|
||||||
* This class contains a list of built-in Guzzle request options.
|
* This class contains a list of built-in Guzzle request options.
|
||||||
*
|
*
|
||||||
* More documentation for each option can be found at http://guzzlephp.org/.
|
* @see https://docs.guzzlephp.org/en/latest/request-options.html
|
||||||
*
|
|
||||||
* @see http://docs.guzzlephp.org/en/v6/request-options.html
|
|
||||||
*/
|
*/
|
||||||
final class RequestOptions
|
final class RequestOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -176,14 +176,13 @@ No system CA bundle could be found in any of the the common system locations.
|
||||||
PHP versions earlier than 5.6 are not properly configured to use the system's
|
PHP versions earlier than 5.6 are not properly configured to use the system's
|
||||||
CA bundle by default. In order to verify peer certificates, you will need to
|
CA bundle by default. In order to verify peer certificates, you will need to
|
||||||
supply the path on disk to a certificate bundle to the 'verify' request
|
supply the path on disk to a certificate bundle to the 'verify' request
|
||||||
option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
|
option: https://docs.guzzlephp.org/en/latest/request-options.html#verify. If
|
||||||
need a specific certificate bundle, then Mozilla provides a commonly used CA
|
you do not need a specific certificate bundle, then Mozilla provides a commonly
|
||||||
bundle which can be downloaded here (provided by the maintainer of cURL):
|
used CA bundle which can be downloaded here (provided by the maintainer of
|
||||||
https://curl.haxx.se/ca/cacert.pem. Once
|
cURL): https://curl.haxx.se/ca/cacert.pem. Once you have a CA bundle available
|
||||||
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
|
on disk, you can set the 'openssl.cafile' PHP ini setting to point to the path
|
||||||
ini setting to point to the path to the file, allowing you to omit the 'verify'
|
to the file, allowing you to omit the 'verify' request option. See
|
||||||
request option. See https://curl.haxx.se/docs/sslcerts.html for more
|
https://curl.haxx.se/docs/sslcerts.html for more information.
|
||||||
information.
|
|
||||||
EOT
|
EOT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue