SimplePieFetch.php 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. <?php
  2. declare(strict_types=1);
  3. final class FreshRSS_SimplePieFetch extends \SimplePie\File
  4. {
  5. public function __construct(string $url, int $timeout = 10, int $redirects = 5,
  6. ?array $headers = null, ?string $useragent = null, bool $force_fsockopen = false, array $curl_options = []) {
  7. // Remove case-insensitively default HTTP headers passed by $headers if they are overridden by $curl_options[CURLOPT_HTTPHEADER]
  8. if (is_array($curl_options[CURLOPT_HTTPHEADER] ?? null) && is_array($headers)) {
  9. $existingKeys = [];
  10. foreach ($curl_options[CURLOPT_HTTPHEADER] as $headerLine) {
  11. if (!is_string($headerLine)) {
  12. continue;
  13. }
  14. $parts = explode(':', $headerLine, 2);
  15. if (count($parts) >= 2) {
  16. $existingKeys[strtolower(trim($parts[0]))] = true;
  17. }
  18. }
  19. foreach ($headers as $key => $value) {
  20. if (isset($existingKeys[strtolower($key)])) {
  21. unset($headers[$key]);
  22. }
  23. }
  24. }
  25. $redirects = $curl_options[CURLOPT_MAXREDIRS] ?? null;
  26. if (!is_int($redirects)) {
  27. $redirects = 4;
  28. } elseif ($redirects < 0) {
  29. $redirects = -1; // infinite redirects
  30. }
  31. if (isset($curl_options[CURLOPT_FOLLOWLOCATION])) {
  32. if ($curl_options[CURLOPT_FOLLOWLOCATION] == true) {
  33. unset($curl_options[CURLOPT_FOLLOWLOCATION]); // Favour the custom SimplePie redirects for security
  34. } else {
  35. $curl_options[CURLOPT_FOLLOWLOCATION] = false;
  36. unset($curl_options[CURLOPT_MAXREDIRS]);
  37. $redirects = 0;
  38. }
  39. }
  40. parent::__construct($url, $timeout, $redirects, $headers, $useragent, $force_fsockopen, $curl_options);
  41. }
  42. #[\Override]
  43. protected function get_curl_resolve_info(string $url, bool $for_proxy = false): array|string|null|false {
  44. return FreshRSS_http_Util::getCurlResolveInfo($url, $for_proxy);
  45. }
  46. #[\Override]
  47. protected function on_http_response($response, array $curl_options = []): void {
  48. if (!\SimplePie\Misc::is_remote_uri($this->get_final_requested_uri())) {
  49. $this->set_status_code(0);
  50. $this->set_body_content('');
  51. $this->error = 'Fetching non-remote URLs is not permitted: “' . $this->get_final_requested_uri() . '“';
  52. $this->success = false;
  53. $response = '';
  54. }
  55. if (FreshRSS_Context::systemConf()->simplepie_syslog_enabled) {
  56. syslog(LOG_INFO, 'FreshRSS SimplePie GET ' . $this->get_status_code() . ' ' . \SimplePie\Misc::url_remove_credentials($this->get_final_requested_uri()));
  57. }
  58. if (in_array($this->get_status_code(), [429, 503], true)) {
  59. $parser = new \SimplePie\HTTP\Parser(is_string($response) ? $response : '');
  60. if ($parser->parse()) {
  61. $headers = $parser->headers;
  62. } else {
  63. $headers = [];
  64. }
  65. $proxy = is_string($curl_options[CURLOPT_PROXY] ?? null) ? $curl_options[CURLOPT_PROXY] : '';
  66. $retryAfter = FreshRSS_http_Util::setRetryAfter($this->get_final_requested_uri(), $proxy, $headers['retry-after'] ?? '');
  67. if ($retryAfter > 0) {
  68. $domain = parse_url($this->get_final_requested_uri(), PHP_URL_HOST);
  69. if (is_string($domain) && $domain !== '') {
  70. if (is_int($port = parse_url($this->get_final_requested_uri(), PHP_URL_PORT))) {
  71. $domain .= ':' . $port;
  72. }
  73. $errorMessage = 'Will retry after ' . date('c', $retryAfter) . ' for domain `' . $domain . '`';
  74. Minz_Log::notice($errorMessage);
  75. }
  76. }
  77. }
  78. }
  79. }