Af_Proxy_Http   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
eloc 131
c 3
b 1
f 1
dl 0
loc 263
rs 9.28
wmc 39

11 Methods

Rating   Name   Duplication   Size   Complexity  
A hook_render_article() 0 2 1
A about() 0 4 1
A init() 0 9 1
A is_public_method() 0 2 1
A hook_enclosure_entry() 0 8 2
A api_version() 0 2 1
A hook_prefs_tab() 0 41 2
B imgproxy() 0 61 8
B hook_render_article_cdm() 0 53 10
A save() 0 8 1
B rewrite_url_if_needed() 0 37 11
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
The constant MAX_CACHE_FILE_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
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
Bug introduced by
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 ignore-type  annotation

77
                print /** @scrutinizer ignore-type */ $data;
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
Bug introduced by
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 ignore-type  annotation

86
                    /*$bg =*/ imagecolorallocate(/** @scrutinizer ignore-type */ $img, 255, 255, 255);
Loading history...
87
                    $textcolor = imagecolorallocate($img, 255, 0, 0);
88
89
                    imagerectangle($img, 0, 0, 450 - 1, 75 - 1, $textcolor);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

89
                    imagerectangle(/** @scrutinizer ignore-type */ $img, 0, 0, 450 - 1, 75 - 1, $textcolor);
Loading history...
90
91
                    imagestring($img, 5, 5, 5, "Proxy request failed", $textcolor);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

91
                    imagestring(/** @scrutinizer ignore-type */ $img, 5, 5, 5, "Proxy request failed", $textcolor);
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
Bug introduced by
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 ignore-type  annotation

96
                    print imagepng(/** @scrutinizer ignore-type */ $img);
Loading history...
97
                    imagedestroy($img);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

97
                    imagedestroy(/** @scrutinizer ignore-type */ $img);
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 "&nbsp;<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 "&nbsp;<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