LumenerController::_runGetBuffer()   A
last analyzed

Complexity

Conditions 6
Paths 7

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 28
rs 9.2222
c 0
b 0
f 0
cc 6
nc 7
nop 3
1
<?php
2
namespace Lumener\Controllers;
3
4
use Illuminate\Routing\Controller;
5
use Illuminate\Http\Request;
6
use Illuminate\Support\Facades\Artisan;
7
8
class LumenerController extends Controller
9
{
10
    protected $adminer;
11
    protected $adminer_object;
12
    protected $plugins_path;
13
    protected $allowed_dbs;
14
    protected $protected_dbs;
15
    protected $request;
16
    protected $mimes;
17
18
    public function __construct(Request $request)
19
    {
20
        if (method_exists(\Route::class, 'hasMiddlewareGroup')
21
        && \Route::hasMiddlewareGroup('lumener')) {
22
            $this->middleware('lumener');
23
        }
24
        // LumenerServiceProvider::register holds the middleware register
25
        // so it does not need to be addeed manually.
26
        // User-defined middleware is handled during route definition for Lumen
27
        $this->allowed_dbs = config('lumener.security.allowed_db');
28
        $this->protected_dbs = config('lumener.security.protected_db');
29
        $this->adminer = LUMENER_STORAGE.'/adminer.php';
30
        $this->adminer_object = __DIR__.'/../logic/adminer_object.php';
31
        $this->plugins_path = LUMENER_STORAGE.'/plugins';
32
        $this->request = $request;
33
        $this->mimes = new \Mimey\MimeTypes;
34
    }
35
36
    public function __call($method, $params)
37
    {
38
        if (strncasecmp($method, "get", 3) === 0) {
39
            $var = preg_replace_callback('/[A-Z]/', function ($c) {
40
                return '_'.strtolower($c[0]);
41
            }, lcfirst(substr($method, 3)));
42
            return $this->$var;
43
        }
44
    }
45
46
    public function index()
47
    {
48
        if ($this->request->cookie('adminer_logged_out')
49
            && config('lumener.logout_redirect')) {
50
            return redirect(config('lumener.logout_redirect'));
51
        }
52
        if (isset($_POST['logout'])) {
53
            $t = encrypt(time());
54
            $h = "Set-Cookie: adminer_logged_out={$t}; expires=".gmdate(
55
                "D, d M Y H:i:s",
56
                time() + config('lumener.logout_cooldown', 10)
57
            )." GMT; path=".preg_replace('~\?.*~', '', $_SERVER["REQUEST_URI"]);
58
            header($h);
59
        }
60
        if (file_exists($this->adminer)) {
61
            return $this->_runAdminer();
62
        } else {
63
            return '<div style="text-align:center;color: red;
64
                                margin-top: 200px;font-weight:bold;">
65
                      Adminer was NOT found.
66
                      Run <span style="color:lightgreen;background:black;
67
                                       padding: 5px;border: 5px dashed white;">
68
                                       php artisan lumener:update --force</span>
69
                                       to fix any issues.
70
                    </div>
71
            ';
72
        }
73
    }
74
75
    public function update()
76
    {
77
        Artisan::call('lumener:update');
78
        return nl2br(Artisan::output());
79
    }
80
81
    public function getResource()
82
    {
83
        $file = $this->request->get('file');
84
        $path = realpath(LUMENER_STORAGE."/{$file}");
85
        // Prevent risky file fetching
86
        // This check is very important, it's a major security risk to allow
87
        // Fetching files outside the LUMENER_STORAGE directory
88
        if (
89
            $path === false
90
            || strncmp($path, LUMENER_STORAGE, strlen(LUMENER_STORAGE)) !== 0
91
        ) {
92
            abort(403);
93
        }
94
        $type = $this->request->get('type', mime_content_type($path));
95
        return response()->download($path, $file, ["Content-Type"=>$type]);
96
    }
97
98
    public function isDBBlocked($db)
99
    {
100
        return
101
        (
102
            $this->allowed_dbs !== null
103
            && !in_array($db, $this->allowed_dbs)
104
        )
105
        ||
106
        (
107
            $this->protected_dbs !== null
108
            && in_array($db, $this->protected_dbs)
109
        );
110
    }
111
112
    private function _runAdminer()
113
    {
114
        $this->_handleAdminerAutoLogin();
115
116
        // Known Issues
117
        $this->_patchAdminerRequest();
118
119
        // Security Check
120
        $this->_verifyAdminerRequest();
121
122
        $content =
123
            $this->_runGetBuffer(
124
                [$this->adminer_object, $this->adminer],
125
                [E_WARNING],
126
                "/LUMENER_OVERRIDE_exit/"
127
            );
128
        $pos = strpos($content, "<!DOCTYPE html>");
129
        if ($pos === false) {
130
            die($content);
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
131
        }
132
133
        // This is a work-around for a strange issue where the error
134
        // that happens in place of exit does not stop execution and the html
135
        // is rendered after the CSS/JS/Image file
136
        if ($pos != 0) {
137
            if (isset($_GET['file'])) {
138
                $type = $this->_guessFileType($_GET['file']);
139
                header("Content-Type: {$type}");
140
            }
141
            die(substr($content, 0, $pos));
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
142
        }
143
144
        return $content;
145
    }
146
147
    private function _guessFileType($name)
148
    {
149
        $ext = end(explode('.', $name));
150
        return $this->mimes->getMimeType($ext);
151
    }
152
153
    private function _handleAdminerAutoLogin()
154
    {
155
        if (!isset($_GET['username']) && !isset($_POST['auth'])
156
            && config('lumener.auto_login')
157
            && !$this->request->cookie('adminer_logged_out')) {
158
            // Skip login screen
159
            $_GET['username'] =
160
                config('lumener.db.username', env("DB_USERNAME"));
161
            $_GET['db'] =
162
                config('lumener.db.database', env("DB_DATABASE"));
163
            // Password is set in the adminer extension
164
        }
165
    }
166
167
    private function _verifyAdminerRequest()
168
    {
169
        if ((isset($_GET['db']) && $_GET['db']
170
            && $this->isDBBlocked($_GET['db']))
171
        || (isset($_POST['auth']['db']) && $_POST['auth']['db']
172
            && $this->isDBBlocked($_POST['auth']['db']))) {
173
            abort(403);
174
        }
175
    }
176
177
    private function _patchAdminerRequest()
178
    {
179
        if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
180
            $_SERVER['HTTP_IF_MODIFIED_SINCE'] = null;
181
        }
182
    }
183
184
    private function _runGetBuffer(
185
        $files,
186
        $termination_errors,
187
        $err_pattern
188
    ) {
189
        // Prepare for unhandled errors
190
        // Termination errors are not necessarily going to be thrown
191
        $this->_setupErrorHandling($termination_errors, $err_pattern);
192
        // Require files
193
        ob_implicit_flush(0);
194
        ob_start();
195
        try {
196
            foreach ($files as $file) {
197
                // require because include will not throw the adminer_exit error
198
                require($file);
199
            }
200
        } catch (\ErrorException $e) {
201
            if (config('lumener.debug')
202
            || !in_array($e->getSeverity(), $termination_errors)) {
203
                throw $e;
204
            }
205
        }
206
        $this->_stopErrorHandling();
207
        $content = "";
208
        while ($level = ob_get_clean()) {
209
            $content = $level . $content;
210
        }
211
        return $content;
212
    }
213
214
    private function _setupErrorHandling($termination_errors, $pattern)
215
    {
216
        $handled = 0;
217
        foreach ($termination_errors as $code) {
218
            $handled |= $code;
219
        }
220
        set_error_handler(
221
            function ($err_severity, $err_msg, $err_file, $err_line) use ($pattern) {
222
                // Check if suppressed with the @-operator
223
                if (0 === error_reporting()) {
224
                    return false;
225
                }
226
                if (preg_match($pattern, $err_msg)) {
227
                    throw new \ErrorException($err_msg, $err_severity, $err_severity, $err_file, $err_line);
228
                }
229
                return false;
230
            },
231
            $handled
232
        );
233
    }
234
235
    private function _stopErrorHandling()
236
    {
237
        set_error_handler(null);
238
    }
239
}
240