Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 318
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
get_usable_memory
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
90
sanitize_data_mssql
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
sanitize_data_oracle
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
sanitize_data_generic
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
fgetd
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
fgetd_seekless
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
acp_database
0.00% covered (danger)
0.00%
0 / 241
0.00% covered (danger)
0.00%
0 / 4
4290
0.00% covered (danger)
0.00%
0 / 1
 main
0.00% covered (danger)
0.00%
0 / 211
0.00% covered (danger)
0.00%
0 / 1
2970
 get_backup_file
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 get_file_list
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 get_supported_extensions
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14/**
15* @ignore
16*/
17if (!defined('IN_PHPBB'))
18{
19    exit;
20}
21
22class acp_database
23{
24    protected $db_tools;
25    protected $temp;
26    public $u_action;
27    public $page_title;
28
29    function main($id, $mode)
30    {
31        global $cache, $db, $user, $template, $table_prefix, $request;
32        global $phpbb_root_path, $phpbb_container, $phpbb_log, $table_prefix;
33
34        $this->db_tools = $phpbb_container->get('dbal.tools');
35        $this->temp = $phpbb_container->get('filesystem.temp');
36        /** @var \phpbb\storage\storage $storage */
37        $storage = $phpbb_container->get('storage.backup');
38
39        $user->add_lang('acp/database');
40
41        $this->tpl_name = 'acp_database';
42        $this->page_title = 'ACP_DATABASE';
43
44        $action    = $request->variable('action', '');
45
46        $form_key = 'acp_database';
47        add_form_key($form_key);
48
49        $template->assign_vars(array(
50            'MODE'    => $mode
51        ));
52
53        switch ($mode)
54        {
55            case 'backup':
56
57                $this->page_title = 'ACP_BACKUP';
58
59                switch ($action)
60                {
61                    case 'download':
62                        $type    = $request->variable('type', '');
63                        $table    = array_intersect($this->db_tools->sql_list_tables(), $request->variable('table', array('')));
64                        $format    = $request->variable('method', '');
65
66                        if (!count($table))
67                        {
68                            trigger_error($user->lang['TABLE_SELECT_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING);
69                        }
70
71                        if (!check_form_key($form_key))
72                        {
73                            trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
74                        }
75
76                        @set_time_limit(1200);
77                        @set_time_limit(0);
78
79                        $time = time();
80
81                        $filename = 'backup_' . $time . '_' . unique_id();
82
83                        try
84                        {
85                            /** @var phpbb\db\extractor\extractor_interface $extractor Database extractor */
86                            $extractor = $phpbb_container->get('dbal.extractor');
87                            $extractor->init_extractor($format, $filename, $time, false, true);
88
89                            $extractor->write_start($table_prefix);
90
91                            foreach ($table as $table_name)
92                            {
93                                // Get the table structure
94                                if ($type == 'full')
95                                {
96                                    // Add table structure to the backup
97                                    // This method also add a "drop the table if exists" after trying to write the table structure
98                                    $extractor->write_table($table_name);
99                                }
100                                else
101                                {
102                                    // Add command to empty table before write data on it
103                                    switch ($db->get_sql_layer())
104                                    {
105                                        case 'sqlite3':
106                                            $extractor->flush('DELETE FROM ' . $table_name . ";\n");
107                                        break;
108
109                                        case 'mssql_odbc':
110                                        case 'mssqlnative':
111                                            $extractor->flush('TRUNCATE TABLE ' . $table_name . "GO\n");
112                                        break;
113
114                                        case 'oracle':
115                                            $extractor->flush('TRUNCATE TABLE ' . $table_name . "/\n");
116                                        break;
117
118                                        default:
119                                            $extractor->flush('TRUNCATE TABLE ' . $table_name . ";\n");
120                                        break;
121                                    }
122                                }
123
124                                // Only supported types are full and data, therefore always write the data
125                                $extractor->write_data($table_name);
126                            }
127
128                            $extractor->write_end();
129                        }
130                        catch (\phpbb\exception\runtime_exception $e)
131                        {
132                            trigger_error($e->getMessage(), E_USER_ERROR);
133                        }
134
135                        try
136                        {
137                            // Get file name
138                            switch ($format)
139                            {
140                                case 'text':
141                                    $ext = '.sql';
142                                break;
143
144                                case 'bzip2':
145                                    $ext = '.sql.gz2';
146                                break;
147
148                                case 'gzip':
149                                    $ext = '.sql.gz';
150                                break;
151                            }
152
153                            $file = $filename . $ext;
154
155                            // Copy to storage using streams
156                            $temp_dir = $this->temp->get_dir();
157
158                            $fp = fopen($temp_dir . '/' . $file, 'rb');
159
160                            if ($fp === false)
161                            {
162                                throw new \phpbb\exception\runtime_exception('CANNOT_OPEN_FILE');
163                            }
164
165                            $storage->write($file, $fp);
166
167                            // Remove file from tmp
168                            @unlink($temp_dir . '/' . $file);
169
170                            // Save to database
171                            $sql = "INSERT INTO " . $table_prefix . "backups (filename)
172                                VALUES ('$file');";
173                            $db->sql_query($sql);
174                        }
175                        catch (\phpbb\exception\runtime_exception $e)
176                        {
177                            trigger_error($e->getMessage(), E_USER_ERROR);
178                        }
179
180                        $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_BACKUP');
181
182                        trigger_error($user->lang['BACKUP_SUCCESS'] . adm_back_link($this->u_action));
183                    break;
184
185                    default:
186                        $tables = $this->db_tools->sql_list_tables();
187                        asort($tables);
188                        foreach ($tables as $table_name)
189                        {
190                            if (strlen($table_prefix) === 0 || stripos($table_name, $table_prefix) === 0)
191                            {
192                                $template->assign_block_vars('tables', array(
193                                    'TABLE'    => $table_name
194                                ));
195                            }
196                        }
197                        unset($tables);
198
199                        $template->assign_vars(array(
200                            'U_ACTION'    => $this->u_action . '&amp;action=download'
201                        ));
202
203                        $available_methods = array('gzip' => 'zlib', 'bzip2' => 'bz2');
204
205                        foreach ($available_methods as $type => $module)
206                        {
207                            if (!@extension_loaded($module))
208                            {
209                                continue;
210                            }
211
212                            $template->assign_block_vars('methods', array(
213                                'TYPE'    => $type
214                            ));
215                        }
216
217                        $template->assign_block_vars('methods', array(
218                            'TYPE'    => 'text'
219                        ));
220                    break;
221                }
222            break;
223
224            case 'restore':
225
226                $this->page_title = 'ACP_RESTORE';
227
228                switch ($action)
229                {
230                    case 'submit':
231                        $delete = $request->variable('delete', '');
232                        $file = $request->variable('file', '');
233
234                        $backup_info = $this->get_backup_file($db, $file);
235
236                        if (empty($backup_info))
237                        {
238                            trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
239                        }
240
241                        if ($delete)
242                        {
243                            if (confirm_box(true))
244                            {
245                                try
246                                {
247                                    // Delete from storage
248                                    $storage->delete($backup_info['file_name']);
249
250                                    // Add log entry
251                                    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_DELETE');
252
253                                    // Remove from database
254                                    $sql = "DELETE FROM " . $table_prefix . "backups
255                                        WHERE filename = '" . $db->sql_escape($backup_info['file_name']) . "';";
256                                    $db->sql_query($sql);
257                                }
258                                catch (\Exception $e)
259                                {
260                                    trigger_error($user->lang['BACKUP_DELETE_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING);
261                                }
262
263                                trigger_error($user->lang['BACKUP_DELETE'] . adm_back_link($this->u_action));
264                            }
265                            else
266                            {
267                                confirm_box(false, $user->lang['DELETE_SELECTED_BACKUP'], build_hidden_fields(array('delete' => $delete, 'file' => $file)));
268                            }
269                        }
270                        else if (confirm_box(true))
271                        {
272                            // Copy file to temp folder to decompress it
273                            $temp_file_name = $this->temp->get_dir() . '/' . $backup_info['file_name'];
274
275                            try
276                            {
277                                $stream = $storage->read($backup_info['file_name']);
278                                $fp = fopen($temp_file_name, 'w+b');
279
280                                stream_copy_to_stream($stream, $fp);
281
282                                fclose($fp);
283                                fclose($stream);
284                            }
285                            catch (\phpbb\storage\exception\storage_exception $e)
286                            {
287                                trigger_error($user->lang['RESTORE_DOWNLOAD_FAIL'] . adm_back_link($this->u_action));
288                            }
289
290                            switch ($backup_info['extension'])
291                            {
292                                case 'sql':
293                                    $fp = fopen($temp_file_name, 'rb');
294                                    $read = 'fread';
295                                    $seek = 'fseek';
296                                    $eof = 'feof';
297                                    $close = 'fclose';
298                                    $fgetd = 'fgetd';
299                                break;
300
301                                case 'sql.bz2':
302                                    $fp = bzopen($temp_file_name, 'r');
303                                    $read = 'bzread';
304                                    $seek = '';
305                                    $eof = 'feof';
306                                    $close = 'bzclose';
307                                    $fgetd = 'fgetd_seekless';
308                                break;
309
310                                case 'sql.gz':
311                                    $fp = gzopen($temp_file_name, 'rb');
312                                    $read = 'gzread';
313                                    $seek = 'gzseek';
314                                    $eof = 'gzeof';
315                                    $close = 'gzclose';
316                                    $fgetd = 'fgetd';
317                                break;
318
319                                default:
320                                    @unlink($temp_file_name);
321                                    trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
322                                    return;
323                            }
324
325                            switch ($db->get_sql_layer())
326                            {
327                                case 'mysqli':
328                                case 'sqlite3':
329                                    while (($sql = $fgetd($fp, ";\n", $read, $seek, $eof)) !== false)
330                                    {
331                                        $db->sql_query($sql);
332                                    }
333                                break;
334
335                                case 'postgres':
336                                    $delim = ";\n";
337                                    while (($sql = $fgetd($fp, $delim, $read, $seek, $eof)) !== false)
338                                    {
339                                        $query = trim($sql);
340
341                                        if (substr($query, 0, 13) == 'CREATE DOMAIN')
342                                        {
343                                            list(, , $domain) = explode(' ', $query);
344                                            $sql = "SELECT domain_name
345                                                FROM information_schema.domains
346                                                WHERE domain_name = '$domain';";
347                                            $result = $db->sql_query($sql);
348                                            if (!$db->sql_fetchrow($result))
349                                            {
350                                                $db->sql_query($query);
351                                            }
352                                            $db->sql_freeresult($result);
353                                        }
354                                        else
355                                        {
356                                            $db->sql_query($query);
357                                        }
358
359                                        if (substr($query, 0, 4) == 'COPY')
360                                        {
361                                            while (($sub = $fgetd($fp, "\n", $read, $seek, $eof)) !== '\.')
362                                            {
363                                                if ($sub === false)
364                                                {
365                                                    trigger_error($user->lang['RESTORE_FAILURE'] . adm_back_link($this->u_action), E_USER_WARNING);
366                                                }
367                                                pg_put_line($db->get_db_connect_id(), $sub . "\n");
368                                            }
369                                            pg_put_line($db->get_db_connect_id(), "\\.\n");
370                                            pg_end_copy($db->get_db_connect_id());
371                                        }
372                                    }
373                                break;
374
375                                case 'oracle':
376                                    while (($sql = $fgetd($fp, "/\n", $read, $seek, $eof)) !== false)
377                                    {
378                                        $db->sql_query($sql);
379                                    }
380                                break;
381
382                                case 'mssql_odbc':
383                                case 'mssqlnative':
384                                    while (($sql = $fgetd($fp, "GO\n", $read, $seek, $eof)) !== false)
385                                    {
386                                        $db->sql_query($sql);
387                                    }
388                                break;
389                            }
390
391                            $close($fp);
392
393                            @unlink($temp_file_name);
394
395                            // Purge the cache due to updated data
396                            $cache->purge();
397
398                            $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_RESTORE');
399                            trigger_error($user->lang['RESTORE_SUCCESS'] . adm_back_link($this->u_action));
400                            break;
401                        }
402                        else
403                        {
404                            confirm_box(false, $user->lang['RESTORE_SELECTED_BACKUP'], build_hidden_fields(array('file' => $file)));
405                        }
406
407                    default:
408                        $backup_files = $this->get_file_list($db);
409
410                        if (!empty($backup_files))
411                        {
412                            krsort($backup_files);
413
414                            foreach ($backup_files as $name => $file)
415                            {
416                                $template->assign_block_vars('files', array(
417                                    'FILE'        => sha1($file),
418                                    'NAME'        => $user->format_date($name, 'd-m-Y H:i', true),
419                                    'SUPPORTED'    => true,
420                                ));
421                            }
422                        }
423
424                        $template->assign_vars(array(
425                            'U_ACTION'    => $this->u_action . '&amp;action=submit'
426                        ));
427                    break;
428                }
429            break;
430        }
431    }
432
433    /**
434     * Get backup file from file hash
435     *
436     * @param \phpbb\db\driver\driver_interface $db Database driver
437     * @param string $file_hash Hash of selected file
438     *
439     * @return array Backup file data or empty array if unable to find file
440     */
441    protected function get_backup_file($db, $file_hash)
442    {
443        $backup_data = [];
444
445        $file_list = $this->get_file_list($db);
446        $supported_extensions = $this->get_supported_extensions();
447
448        foreach ($file_list as $file)
449        {
450            preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $file, $matches);
451            if (sha1($file) === $file_hash && in_array($matches[2], $supported_extensions))
452            {
453                $backup_data = [
454                    'file_name' => $file,
455                    'extension' => $matches[2],
456                ];
457                break;
458            }
459        }
460
461        return $backup_data;
462    }
463
464    /**
465     * Get backup file list for directory
466     *
467     * @param \phpbb\db\driver\driver_interface $db Database driver
468     *
469     * @return array List of backup files in specified directory
470     */
471    protected function get_file_list($db)
472    {
473        $supported_extensions = $this->get_supported_extensions();
474
475        $backup_files = [];
476
477        $sql = 'SELECT filename
478            FROM ' . BACKUPS_TABLE;
479        $result = $db->sql_query($sql);
480
481        while ($row = $db->sql_fetchrow($result))
482        {
483            if (preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $row['filename'], $matches))
484            {
485                if (in_array($matches[2], $supported_extensions))
486                {
487                    $backup_files[(int) $matches[1]] = $row['filename'];
488                }
489            }
490        }
491        $db->sql_freeresult($result);
492
493        return $backup_files;
494    }
495
496    /**
497     * Get supported extensions for backup
498     *
499     * @return array List of supported extensions
500     */
501    protected function get_supported_extensions()
502    {
503        $extensions = ['sql'];
504        $available_methods = ['sql.gz' => 'zlib', 'sql.bz2' => 'bz2'];
505
506        foreach ($available_methods as $type => $module)
507        {
508            if (!@extension_loaded($module))
509            {
510                continue;
511            }
512            $extensions[] = $type;
513        }
514
515        return $extensions;
516    }
517}
518
519// get how much space we allow for a chunk of data, very similar to phpMyAdmin's way of doing things ;-) (hey, we only do this for MySQL anyway :P)
520function get_usable_memory()
521{
522    $val = trim(@ini_get('memory_limit'));
523
524    if (preg_match('/(\\d+)([mkg]?)/i', $val, $regs))
525    {
526        $memory_limit = (int) $regs[1];
527        switch ($regs[2])
528        {
529
530            case 'k':
531            case 'K':
532                $memory_limit *= 1024;
533            break;
534
535            case 'm':
536            case 'M':
537                $memory_limit *= 1048576;
538            break;
539
540            case 'g':
541            case 'G':
542                $memory_limit *= 1073741824;
543            break;
544        }
545
546        // how much memory PHP requires at the start of export (it is really a little less)
547        if ($memory_limit > 6100000)
548        {
549            $memory_limit -= 6100000;
550        }
551
552        // allow us to consume half of the total memory available
553        $memory_limit /= 2;
554    }
555    else
556    {
557        // set the buffer to 1M if we have no clue how much memory PHP will give us :P
558        $memory_limit = 1048576;
559    }
560
561    return $memory_limit;
562}
563
564function sanitize_data_mssql($text)
565{
566    $data = preg_split('/[\n\t\r\b\f]/', $text);
567    preg_match_all('/[\n\t\r\b\f]/', $text, $matches);
568
569    $val = array();
570
571    foreach ($data as $value)
572    {
573        if (strlen($value))
574        {
575            $val[] = "'" . $value . "'";
576        }
577        if (count($matches[0]))
578        {
579            $val[] = 'char(' . ord(array_shift($matches[0])) . ')';
580        }
581    }
582
583    return implode('+', $val);
584}
585
586function sanitize_data_oracle($text)
587{
588//    $data = preg_split('/[\0\n\t\r\b\f\'"\/\\\]/', $text);
589//    preg_match_all('/[\0\n\t\r\b\f\'"\/\\\]/', $text, $matches);
590    $data = preg_split('/[\0\b\f\'\/]/', $text);
591    preg_match_all('/[\0\r\b\f\'\/]/', $text, $matches);
592
593    $val = array();
594
595    foreach ($data as $value)
596    {
597        if (strlen($value))
598        {
599            $val[] = "'" . $value . "'";
600        }
601        if (count($matches[0]))
602        {
603            $val[] = 'chr(' . ord(array_shift($matches[0])) . ')';
604        }
605    }
606
607    return implode('||', $val);
608}
609
610function sanitize_data_generic($text)
611{
612    $data = preg_split('/[\n\t\r\b\f]/', $text);
613    preg_match_all('/[\n\t\r\b\f]/', $text, $matches);
614
615    $val = array();
616
617    foreach ($data as $value)
618    {
619        if (strlen($value))
620        {
621            $val[] = "'" . $value . "'";
622        }
623        if (count($matches[0]))
624        {
625            $val[] = "'" . array_shift($matches[0]) . "'";
626        }
627    }
628
629    return implode('||', $val);
630}
631
632// modified from PHP.net
633function fgetd(&$fp, $delim, $read, $seek, $eof, $buffer = 8192)
634{
635    $record = '';
636    $delim_len = strlen($delim);
637
638    while (!$eof($fp))
639    {
640        $pos = strpos($record, $delim);
641        if ($pos === false)
642        {
643            $record .= $read($fp, $buffer);
644            if ($eof($fp) && ($pos = strpos($record, $delim)) !== false)
645            {
646                $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
647                return substr($record, 0, $pos);
648            }
649        }
650        else
651        {
652            $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
653            return substr($record, 0, $pos);
654        }
655    }
656
657    return false;
658}
659
660function fgetd_seekless(&$fp, $delim, $read, $seek, $eof, $buffer = 8192)
661{
662    static $array = array();
663    static $record = '';
664
665    if (!count($array))
666    {
667        while (!$eof($fp))
668        {
669            if (strpos($record, $delim) !== false)
670            {
671                $array = explode($delim, $record);
672                $record = array_pop($array);
673                break;
674            }
675            else
676            {
677                $record .= $read($fp, $buffer);
678            }
679        }
680        if ($eof($fp) && strpos($record, $delim) !== false)
681        {
682            $array = explode($delim, $record);
683            $record = array_pop($array);
684        }
685    }
686
687    if (count($array))
688    {
689        return array_shift($array);
690    }
691
692    return false;
693}