codysnider /
urss
| 1 | <?php |
||||
| 2 | class Af_Proxy_Http extends Plugin { |
||||
| 3 | |||||
| 4 | /* @var PluginHost $host */ |
||||
| 5 | private $host; |
||||
| 6 | |||||
| 7 | /* @var DiskCache $cache */ |
||||
| 8 | private $cache; |
||||
| 9 | |||||
| 10 | public function about() { |
||||
| 11 | return array(1.0, |
||||
| 12 | "Loads media served over plain HTTP via built-in secure proxy", |
||||
| 13 | "fox"); |
||||
| 14 | } |
||||
| 15 | |||||
| 16 | private $ssl_known_whitelist = "imgur.com gfycat.com i.reddituploads.com pbs.twimg.com i.redd.it i.sli.mg media.tumblr.com"; |
||||
| 17 | |||||
| 18 | public function is_public_method($method) { |
||||
| 19 | return $method === "imgproxy"; |
||||
| 20 | } |
||||
| 21 | |||||
| 22 | public function init($host) { |
||||
| 23 | $this->host = $host; |
||||
| 24 | $this->cache = new DiskCache("images"); |
||||
| 25 | |||||
| 26 | $host->add_hook($host::HOOK_RENDER_ARTICLE, $this, 150); |
||||
| 27 | $host->add_hook($host::HOOK_RENDER_ARTICLE_CDM, $this, 150); |
||||
| 28 | $host->add_hook($host::HOOK_ENCLOSURE_ENTRY, $this); |
||||
| 29 | |||||
| 30 | $host->add_hook($host::HOOK_PREFS_TAB, $this); |
||||
| 31 | } |
||||
| 32 | |||||
| 33 | public function hook_enclosure_entry($enc) { |
||||
| 34 | if (preg_match("/image/", $enc["content_type"])) { |
||||
| 35 | $proxy_all = $this->host->get($this, "proxy_all"); |
||||
| 36 | |||||
| 37 | $enc["content_url"] = $this->rewrite_url_if_needed($enc["content_url"], $proxy_all); |
||||
| 38 | } |
||||
| 39 | |||||
| 40 | return $enc; |
||||
| 41 | } |
||||
| 42 | |||||
| 43 | public function hook_render_article($article) { |
||||
| 44 | return $this->hook_render_article_cdm($article); |
||||
| 45 | } |
||||
| 46 | |||||
| 47 | public function imgproxy() { |
||||
| 48 | |||||
| 49 | $url = rewrite_relative_url(get_self_url_prefix(), $_REQUEST["url"]); |
||||
| 50 | |||||
| 51 | // called without user context, let's just redirect to original URL |
||||
| 52 | if (!$_SESSION["uid"]) { |
||||
| 53 | header("Location: $url"); |
||||
| 54 | return; |
||||
| 55 | } |
||||
| 56 | |||||
| 57 | $local_filename = sha1($url); |
||||
| 58 | |||||
| 59 | if ($this->cache->exists($local_filename)) { |
||||
| 60 | header("Location: ".$this->cache->getUrl($local_filename)); |
||||
| 61 | return; |
||||
| 62 | //$this->cache->send($local_filename); |
||||
| 63 | } else { |
||||
| 64 | $data = fetch_file_contents(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]); |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 65 | |||||
| 66 | if ($data) { |
||||
| 67 | |||||
| 68 | $disable_cache = $this->host->get($this, "disable_cache"); |
||||
| 69 | |||||
| 70 | if (!$disable_cache) { |
||||
| 71 | if ($this->cache->put($local_filename, $data)) { |
||||
| 72 | header("Location: ".$this->cache->getUrl($local_filename)); |
||||
| 73 | return; |
||||
| 74 | } |
||||
| 75 | } |
||||
| 76 | |||||
| 77 | print $data; |
||||
|
0 ignored issues
–
show
Are you sure
$data of type string|true can be used in print()?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 78 | } else { |
||||
| 79 | global $fetch_last_error; |
||||
| 80 | global $fetch_last_error_code; |
||||
| 81 | global $fetch_last_error_content; |
||||
| 82 | |||||
| 83 | if (function_exists("imagecreate") && !isset($_REQUEST["text"])) { |
||||
| 84 | $img = imagecreate(450, 75); |
||||
| 85 | |||||
| 86 | /*$bg =*/ imagecolorallocate($img, 255, 255, 255); |
||||
|
0 ignored issues
–
show
It seems like
$img can also be of type false; however, parameter $image of imagecolorallocate() does only seem to accept resource, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 87 | $textcolor = imagecolorallocate($img, 255, 0, 0); |
||||
| 88 | |||||
| 89 | imagerectangle($img, 0, 0, 450 - 1, 75 - 1, $textcolor); |
||||
|
0 ignored issues
–
show
It seems like
$img can also be of type false; however, parameter $image of imagerectangle() does only seem to accept resource, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 90 | |||||
| 91 | imagestring($img, 5, 5, 5, "Proxy request failed", $textcolor); |
||||
|
0 ignored issues
–
show
It seems like
$img can also be of type false; however, parameter $image of imagestring() does only seem to accept resource, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 92 | imagestring($img, 5, 5, 30, truncate_middle($url, 46, "..."), $textcolor); |
||||
| 93 | imagestring($img, 5, 5, 55, "HTTP Code: $fetch_last_error_code", $textcolor); |
||||
| 94 | |||||
| 95 | header("Content-type: image/png"); |
||||
| 96 | print imagepng($img); |
||||
|
0 ignored issues
–
show
It seems like
$img can also be of type false; however, parameter $image of imagepng() does only seem to accept resource, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 97 | imagedestroy($img); |
||||
|
0 ignored issues
–
show
It seems like
$img can also be of type false; however, parameter $image of imagedestroy() does only seem to accept resource, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 98 | |||||
| 99 | } else { |
||||
| 100 | header("Content-type: text/html"); |
||||
| 101 | |||||
| 102 | http_response_code(400); |
||||
| 103 | |||||
| 104 | print "<h1>Proxy request failed.</h1>"; |
||||
| 105 | print "<p>Fetch error $fetch_last_error ($fetch_last_error_code)</p>"; |
||||
| 106 | print "<p>URL: $url</p>"; |
||||
| 107 | print "<textarea cols='80' rows='25'>".htmlspecialchars($fetch_last_error_content)."</textarea>"; |
||||
| 108 | } |
||||
| 109 | } |
||||
| 110 | } |
||||
| 111 | } |
||||
| 112 | |||||
| 113 | private function rewrite_url_if_needed($url, $all_remote = false) { |
||||
| 114 | /* we don't need to handle URLs where local cache already exists, tt-rss rewrites those automatically */ |
||||
| 115 | if (!$this->cache->exists(sha1($url))) { |
||||
| 116 | |||||
| 117 | $scheme = parse_url($url, PHP_URL_SCHEME); |
||||
| 118 | |||||
| 119 | if ($all_remote) { |
||||
| 120 | $host = parse_url($url, PHP_URL_HOST); |
||||
| 121 | $self_host = parse_url(get_self_url_prefix(), PHP_URL_HOST); |
||||
| 122 | |||||
| 123 | $is_remote = $host != $self_host; |
||||
| 124 | } else { |
||||
| 125 | $is_remote = false; |
||||
| 126 | } |
||||
| 127 | |||||
| 128 | if (($scheme != 'https' && $scheme != "") || $is_remote) { |
||||
| 129 | if (strpos($url, "data:") !== 0) { |
||||
| 130 | $parts = parse_url($url); |
||||
| 131 | |||||
| 132 | foreach (explode(" ", $this->ssl_known_whitelist) as $host) { |
||||
| 133 | if (substr(strtolower($parts['host']), -strlen($host)) === strtolower($host)) { |
||||
| 134 | $parts['scheme'] = 'https'; |
||||
| 135 | $url = build_url($parts); |
||||
| 136 | if ($all_remote && $is_remote) { |
||||
| 137 | break; |
||||
| 138 | } else { |
||||
| 139 | return $url; |
||||
| 140 | } |
||||
| 141 | } |
||||
| 142 | } |
||||
| 143 | |||||
| 144 | return $this->host->get_public_method_url($this, "imgproxy", ["url" => $url]); |
||||
| 145 | } |
||||
| 146 | } |
||||
| 147 | } |
||||
| 148 | |||||
| 149 | return $url; |
||||
| 150 | } |
||||
| 151 | |||||
| 152 | /** |
||||
| 153 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
||||
| 154 | */ |
||||
| 155 | public function hook_render_article_cdm($article, $api_mode = false) { |
||||
| 156 | |||||
| 157 | $need_saving = false; |
||||
| 158 | $proxy_all = $this->host->get($this, "proxy_all"); |
||||
| 159 | |||||
| 160 | $doc = new DOMDocument(); |
||||
| 161 | if (@$doc->loadHTML('<?xml encoding="UTF-8">'.$article["content"])) { |
||||
| 162 | $xpath = new DOMXPath($doc); |
||||
| 163 | $imgs = $xpath->query("//img[@src]"); |
||||
| 164 | |||||
| 165 | foreach ($imgs as $img) { |
||||
| 166 | $new_src = $this->rewrite_url_if_needed($img->getAttribute("src"), $proxy_all); |
||||
| 167 | |||||
| 168 | if ($new_src != $img->getAttribute("src")) { |
||||
| 169 | $img->setAttribute("src", $new_src); |
||||
| 170 | $img->removeAttribute("srcset"); |
||||
| 171 | |||||
| 172 | $need_saving = true; |
||||
| 173 | } |
||||
| 174 | } |
||||
| 175 | |||||
| 176 | $vids = $xpath->query("(//video|//picture)"); |
||||
| 177 | |||||
| 178 | foreach ($vids as $vid) { |
||||
| 179 | if ($vid->hasAttribute("poster")) { |
||||
| 180 | $new_src = $this->rewrite_url_if_needed($vid->getAttribute("poster"), $proxy_all); |
||||
| 181 | |||||
| 182 | if ($new_src != $vid->getAttribute("poster")) { |
||||
| 183 | $vid->setAttribute("poster", $new_src); |
||||
| 184 | |||||
| 185 | $need_saving = true; |
||||
| 186 | } |
||||
| 187 | } |
||||
| 188 | |||||
| 189 | $vsrcs = $xpath->query("source", $vid); |
||||
| 190 | |||||
| 191 | foreach ($vsrcs as $vsrc) { |
||||
| 192 | $new_src = $this->rewrite_url_if_needed($vsrc->getAttribute("src"), $proxy_all); |
||||
| 193 | |||||
| 194 | if ($new_src != $vsrc->getAttribute("src")) { |
||||
| 195 | $vid->setAttribute("src", $new_src); |
||||
| 196 | |||||
| 197 | $need_saving = true; |
||||
| 198 | } |
||||
| 199 | } |
||||
| 200 | } |
||||
| 201 | } |
||||
| 202 | |||||
| 203 | if ($need_saving) { |
||||
| 204 | $article["content"] = $doc->saveHTML(); |
||||
| 205 | } |
||||
| 206 | |||||
| 207 | return $article; |
||||
| 208 | } |
||||
| 209 | |||||
| 210 | public function hook_prefs_tab($args) { |
||||
| 211 | if ($args != "prefFeeds") { |
||||
| 212 | return; |
||||
| 213 | } |
||||
| 214 | |||||
| 215 | print "<div dojoType=\"dijit.layout.AccordionPane\" |
||||
| 216 | title=\"<i class='material-icons'>extension</i> ".__('Image proxy settings (af_proxy_http)')."\">"; |
||||
| 217 | |||||
| 218 | print "<form dojoType=\"dijit.form.Form\">"; |
||||
| 219 | |||||
| 220 | print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> |
||||
| 221 | evt.preventDefault(); |
||||
| 222 | if (this.validate()) { |
||||
| 223 | console.log(dojo.objectToQuery(this.getValues())); |
||||
| 224 | new Ajax.Request('backend.php', { |
||||
| 225 | parameters: dojo.objectToQuery(this.getValues()), |
||||
| 226 | onComplete: function(transport) { |
||||
| 227 | Notify.info(transport.responseText); |
||||
| 228 | } |
||||
| 229 | }); |
||||
| 230 | //this.reset(); |
||||
| 231 | } |
||||
| 232 | </script>"; |
||||
| 233 | |||||
| 234 | print_hidden("op", "pluginhandler"); |
||||
| 235 | print_hidden("method", "save"); |
||||
| 236 | print_hidden("plugin", "af_proxy_http"); |
||||
| 237 | |||||
| 238 | $proxy_all = $this->host->get($this, "proxy_all"); |
||||
| 239 | print_checkbox("proxy_all", $proxy_all); |
||||
| 240 | print " <label for=\"proxy_all\">".__("Enable proxy for all remote images.")."</label><br/>"; |
||||
| 241 | |||||
| 242 | $disable_cache = $this->host->get($this, "disable_cache"); |
||||
| 243 | print_checkbox("disable_cache", $disable_cache); |
||||
| 244 | print " <label for=\"disable_cache\">".__("Don't cache files locally.")."</label>"; |
||||
| 245 | |||||
| 246 | print "<p>"; print_button("submit", __("Save")); |
||||
| 247 | |||||
| 248 | print "</form>"; |
||||
| 249 | |||||
| 250 | print "</div>"; |
||||
| 251 | } |
||||
| 252 | |||||
| 253 | public function save() { |
||||
| 254 | $proxy_all = checkbox_to_sql_bool($_POST["proxy_all"]); |
||||
| 255 | $disable_cache = checkbox_to_sql_bool($_POST["disable_cache"]); |
||||
| 256 | |||||
| 257 | $this->host->set($this, "proxy_all", $proxy_all, false); |
||||
| 258 | $this->host->set($this, "disable_cache", $disable_cache); |
||||
| 259 | |||||
| 260 | echo __("Configuration saved"); |
||||
| 261 | } |
||||
| 262 | |||||
| 263 | public function api_version() { |
||||
| 264 | return 2; |
||||
| 265 | } |
||||
| 266 | } |
||||
| 267 |