1<?php
2/*
3 * vim:set softtabstop=4 shiftwidth=4 expandtab:
4 *
5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
6 * Copyright 2001 - 2020 Ampache.org
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 *
21 */
22
23declare(strict_types=0);
24
25namespace Ampache\Module\Util\Captcha;
26
27use Ampache\Module\System\Core;
28
29/**
30 * Class easy_captcha_utility
31 *
32 * "AJAX" and utility code
33 */
34class easy_captcha_utility
35{
36
37
38    #-- determine usable temp directory
39    /**
40     * @return mixed
41     */
42    public function tmp()
43    {
44        return current(array_filter(// filter by writability
45            array_filter(// filter empty entries
46                array(
47                    $_SERVER['TMPDIR'],
48                    $_SERVER['REDIRECT_TMPDIR'],
49                    $_SERVER['TEMP'],
50                    ini_get('upload_tmp_dir'),
51                    $_SERVER['TMP'],
52                    $_SERVER['TEMPDIR'],
53                    function_exists("sys_get_temp_dir") ? sys_get_temp_dir() : "",
54                    '/tmp'
55                )), "is_writable"));
56    }
57
58
59    #-- script was called directly
60
61    /**
62     * @return boolean
63     */
64    public static function API()
65    {
66
67        #-- load data
68        if ($id = Core::get_get(CAPTCHA_PARAM_ID)) {
69            #-- special case
70            if ($id == 'base.js') {
71                easy_captcha_utility::js_base();
72            } else {
73                $c       = new easy_captcha($id = null, $ignore_expiration = 1);
74                $expired = !$c->is_valid();
75
76                #-- JS-RPC request, check entered solution on the fly
77                if ($test = $_REQUEST[CAPTCHA_PARAM_INPUT]) {
78                    #-- check
79                    if ($expired || empty($c->image)) {
80                        die(easy_captcha_utility::js_header('alert("captcha error: request invalid (wrong storage id) / or expired");'));
81                    }
82                    if (0 >= $c->ajax_tries--) {
83                        $c->log("::API", "JS-RPC", "ajax_tries exhausted ($c->ajax_tries)");
84                    }
85                    $okay = $c->image->solved($test) || $c->text->solved($test);
86
87                    #-- sendresult
88                    easy_captcha_utility::js_rpc($okay);
89                } else {
90                    #-- generate and send image file
91                    if ($expired) {
92                        $type = "image/png";
93                        $bin  = easy_captcha_utility::expired_png();
94                    } else {
95                        $type = "image/jpeg";
96                        $bin  = $c->image->jpeg();
97                    }
98                    header("Pragma: no-cache");
99                    header("Cache-Control: no-cache, no-store, must-revalidate, private");
100                    header("Expires: " . gmdate("r", time()));
101                    header("Content-Length: " . strlen($bin));
102                    header("Content-Type: $type");
103                    print $bin;
104                }
105            }
106
107            return false;
108        }
109
110        return false;
111    }
112
113    #-- hardwired error img
114
115    /**
116     * @return false|string
117     */
118    public function expired_png()
119    {
120        return base64_decode("iVBORw0KGgoAAAANSUhEUgAAADwAAAAUAgMAAACsbba6AAAADFBMVEUeEhFcMjGgWFf9jIrTTikpAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA3UlEQVQY01XPzwoBcRAH8F9RjpSTm9xR9qQwtnX/latX0DrsA3gC8QDK0QO4bv7UOtmM+x4oZ4X5FQc1hlb41dR8mm/9ZhT/P7X/dDcpZPU3FYft9kWbLuWp4Bgt9v1oGG07Ja8ojfjxQFym02DVmoixkV/m2JI/TUtefR7nD9rkrhkC+6D77/8mUhDvw0ymLPwxf8esghEFRq8hqKcu2iG16Vlun1zYTO7RwCeFyoJqAgC3LQwzYiCokDj0MWRxb+Z6R8mPJb8Q77zlPbuCoJE8a/t7P773uv36tdcTmsXfRycoRJ8AAAAASUVORK5CYII=");
121    }
122
123    #-- send base javascript
124    public function js_base()
125    {
126        $captcha_new_urls = $_GET["captcha_new_urls"] ? 0 : 1;
127        $BASE_URL         = CAPTCHA_BASE_URL;
128        $PARAM_ID         = CAPTCHA_PARAM_ID;
129        $PARAM_INPUT      = CAPTCHA_PARAM_INPUT;
130        $COLOR_CALC       = CAPTCHA_INVERSE ? "32 +" : "224 -";
131        easy_captcha_utility::js_header();
132        print<<<END_____BASE__BASE__BASE__BASE__BASE__BASE__BASE__BASE_____END
133
134
135/* easy_captcha utility code */
136
137// global vars
138captcha_url_rx = /(https?:\/\/\w[^\/,\]\[=#]+)/ig;
139captcha_form_urls = new Array();
140captcha_sol_cb = "";
141captcha_rpc = 0;
142
143// set up watchers
144if ($captcha_new_urls) {
145  window.setTimeout("captcha_form_urls = captcha_find_urls_in_form()", 500);
146  window.setInterval("captcha_spamfree_no_new_urls()", 3000);
147}
148
149// scans for URLs in any of the form fields
150function captcha_find_urls_in_form() {
151   var nf, ne, nv;
152   for (nf=0; nf<document.forms.length; nf++) {
153      for (ne=0; ne<document.forms[nf].elements.length; ne++) {
154         nv += "\\n" + document.forms[nf].elements[ne].value;
155      }
156   }
157   var r = nv.match(captcha_url_rx);
158   if (!r) { r = new Array(); }
159   return r;
160}
161// diff URL lists and hide captcha if nothing new was entered
162function captcha_spamfree_no_new_urls() {
163   var has_new_urls = captcha_find_urls_in_form().join(",") != captcha_form_urls.join(",");
164   var s = document.getElementById("captcha").style;
165   if (s.opacity) {
166      s.opacity = has_new_urls ? "0.9" : "0.1";
167   }
168   else {
169      s.display = has_new_urls ? "block" : "none";
170   }
171}
172
173// if a certain solution length is reached, check it remotely (JS-RPC)
174function captcha_check_solution() {
175   var cid = document.getElementById("{$PARAM_ID}");
176   var inf = document.getElementById("{$PARAM_INPUT}");
177   var len = inf.value.length;
178   // visualize processissing
179   if (len >= 4) {
180      inf.style.border = "2px solid #FF9955";
181   }
182   // if enough letters entered
183   if (len >= 4) {
184      // remove old <script> node
185      var scr;
186      if (src = document.getElementById("captcha_ajax_1")) {
187         src.parentNode.removeChild(src);
188      }
189      // create new <script> node, initiate JS-RPC call thereby
190      scr = document.createElement("script");
191      var url = "$BASE_URL" + "?$PARAM_ID=" + cid.value + "&$PARAM_INPUT=" + inf.value;
192      scr.setAttribute("src", url);
193      scr.setAttribute("id", "captcha_ajax_1");
194      document.getElementById("captcha").appendChild(scr);
195      captcha_rpc = 1;
196   }
197   // visual feedback for editing
198   var col = $COLOR_CALC len * 5;
199       col = col.toString(16);
200   inf.style.background = "#"+col+col+col;
201}
202
203
204END_____BASE__BASE__BASE__BASE__BASE__BASE__BASE__BASE_____END;
205    }
206
207    #-- javascript header (also prevent caching)
208
209    /**
210     * @param string $print
211     */
212    public function js_header($print = '')
213    {
214        header("Pragma: no-cache");
215        header("Cache-Control: no-cache, no-store, must-revalidate, private");
216        header("Expires: " . gmdate("r", time()));
217        header("Content-Type: text/javascript");
218        if ($print) {
219            print $print;
220        }
221    }
222
223
224    #-- response javascript
225
226    /**
227     * @param $yes
228     */
229    public function js_rpc($yes)
230    {
231        $yes         = $yes ? 1 : 0;
232        $PARAM_INPUT = CAPTCHA_PARAM_INPUT;
233        easy_captcha_utility::js_header();
234        print<<<END_____JSRPC__JSRPC__JSRPC__JSRPC__JSRPC__JSRPC_____END
235
236
237// JS-RPC response
238if (1) {
239   captcha_rpc = 0;
240   var inf = document.getElementById("{$PARAM_INPUT}");
241   inf.style.borderColor = $yes ? "#22AA22" : "#AA2222";
242}
243
244
245END_____JSRPC__JSRPC__JSRPC__JSRPC__JSRPC__JSRPC_____END;
246    }
247
248    /* static */
249    /**
250     * @param $url
251     * @return string
252     */
253    public function canonical_path($url)
254    {
255        $path = parse_url($url);
256
257        if (is_array($path) && !empty($path['path'])) {
258            $url = $path['path'];
259        }
260
261        $path    = array();
262        $abspath = substr("$url ", 0, 1) == '/' ? '/' : '';
263        $ncomp   = 0;
264
265        foreach (explode('/', $url) as $comp) {
266            switch ($comp) {
267                case '':
268                case '.':
269                    break;
270                case '..':
271                    if ($ncomp--) {
272                        array_pop($path);
273                        break;
274                    }
275                    // Intentional break fall-through
276                default:
277                    $path[] = $comp;
278                    $ncomp++;
279                    break;
280            }
281        }
282
283        $path = $abspath . implode('/', $path);
284
285        return empty($path) ? '.' : $path;
286    }  //patch contributed from Fedora downstream by Patrick Monnerat
287}
288