Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 320
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 / 243
0.00% covered (danger)
0.00%
0 / 4
4422
0.00% covered (danger)
0.00%
0 / 1
 main
0.00% covered (danger)
0.00%
0 / 213
0.00% covered (danger)
0.00%
0 / 1
3080
 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_stream($file, $fp);
166
167                            if (is_resource($fp))
168                            {
169                                fclose($fp);
170                            }
171
172                            // Remove file from tmp
173                            @unlink($temp_dir . '/' . $file);
174
175                            // Save to database
176                            $sql = "INSERT INTO " . $table_prefix . "backups (filename)
177                                VALUES ('$file');";
178                            $db->sql_query($sql);
179                        }
180                        catch (\phpbb\exception\runtime_exception $e)
181                        {
182                            trigger_error($e->getMessage(), E_USER_ERROR);
183                        }
184
185                        $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_BACKUP');
186
187                        trigger_error($user->lang['BACKUP_SUCCESS'] . adm_back_link($this->u_action));
188                    break;
189
190                    default:
191                        $tables = $this->db_tools->sql_list_tables();
192                        asort($tables);
193                        foreach ($tables as $table_name)
194                        {
195                            if (strlen($table_prefix) === 0 || stripos($table_name, $table_prefix) === 0)
196                            {
197                                $template->assign_block_vars('tables', array(
198                                    'TABLE'    => $table_name
199                                ));
200                            }
201                        }
202                        unset($tables);
203
204                        $template->assign_vars(array(
205                            'U_ACTION'    => $this->u_action . '&amp;action=download'
206                        ));
207
208                        $available_methods = array('gzip' => 'zlib', 'bzip2' => 'bz2');
209
210                        foreach ($available_methods as $type => $module)
211                        {
212                            if (!@extension_loaded($module))
213                            {
214                                continue;
215                            }
216
217                            $template->assign_block_vars('methods', array(
218                                'TYPE'    => $type
219                            ));
220                        }
221
222                        $template->assign_block_vars('methods', array(
223                            'TYPE'    => 'text'
224                        ));
225                    break;
226                }
227            break;
228
229            case 'restore':
230
231                $this->page_title = 'ACP_RESTORE';
232
233                switch ($action)
234                {
235                    case 'submit':
236                        $delete = $request->variable('delete', '');
237                        $file = $request->variable('file', '');
238
239                        $backup_info = $this->get_backup_file($db, $file);
240
241                        if (empty($backup_info))
242                        {
243                            trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
244                        }
245
246                        if ($delete)
247                        {
248                            if (confirm_box(true))
249                            {
250                                try
251                                {
252                                    // Delete from storage
253                                    $storage->delete($backup_info['file_name']);
254
255                                    // Add log entry
256                                    $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_DELETE');
257
258                                    // Remove from database
259                                    $sql = "DELETE FROM " . $table_prefix . "backups
260                                        WHERE filename = '" . $db->sql_escape($backup_info['file_name']) . "';";
261                                    $db->sql_query($sql);
262                                }
263                                catch (\Exception $e)
264                                {
265                                    trigger_error($user->lang['BACKUP_DELETE_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING);
266                                }
267
268                                trigger_error($user->lang['BACKUP_DELETE'] . adm_back_link($this->u_action));
269                            }
270                            else
271                            {
272                                confirm_box(false, $user->lang['DELETE_SELECTED_BACKUP'], build_hidden_fields(array('delete' => $delete, 'file' => $file)));
273                            }
274                        }
275                        else if (confirm_box(true))
276                        {
277                            // Copy file to temp folder to decompress it
278                            $temp_file_name = $this->temp->get_dir() . '/' . $backup_info['file_name'];
279
280                            try
281                            {
282                                $stream = $storage->read_stream($backup_info['file_name']);
283                                $fp = fopen($temp_file_name, 'w+b');
284
285                                stream_copy_to_stream($stream, $fp);
286
287                                fclose($fp);
288                                fclose($stream);
289                            }
290                            catch (\phpbb\storage\exception\storage_exception $e)
291                            {
292                                trigger_error($user->lang['RESTORE_DOWNLOAD_FAIL'] . adm_back_link($this->u_action));
293                            }
294
295                            switch ($backup_info['extension'])
296                            {
297                                case 'sql':
298                                    $fp = fopen($temp_file_name, 'rb');
299                                    $read = 'fread';
300                                    $seek = 'fseek';
301                                    $eof = 'feof';
302                                    $close = 'fclose';
303                                    $fgetd = 'fgetd';
304                                break;
305
306                                case 'sql.bz2':
307                                    $fp = bzopen($temp_file_name, 'r');
308                                    $read = 'bzread';
309                                    $seek = '';
310                                    $eof = 'feof';
311                                    $close = 'bzclose';
312                                    $fgetd = 'fgetd_seekless';
313                                break;
314
315                                case 'sql.gz':
316                                    $fp = gzopen($temp_file_name, 'rb');
317                                    $read = 'gzread';
318                                    $seek = 'gzseek';
319                                    $eof = 'gzeof';
320                                    $close = 'gzclose';
321                                    $fgetd = 'fgetd';
322                                break;
323
324                                default:
325                                    @unlink($temp_file_name);
326                                    trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
327                                    return;
328                            }
329
330                            switch ($db->get_sql_layer())
331                            {
332                                case 'mysqli':
333                                case 'sqlite3':
334                                    while (($sql = $fgetd($fp, ";\n", $read, $seek, $eof)) !== false)
335                                    {
336                                        $db->sql_query($sql);
337                                    }
338                                break;
339
340                                case 'postgres':
341                                    $delim = ";\n";
342                                    while (($sql = $fgetd($fp, $delim, $read, $seek, $eof)) !== false)
343                                    {
344                                        $query = trim($sql);
345
346                                        if (substr($query, 0, 13) == 'CREATE DOMAIN')
347                                        {
348                                            list(, , $domain) = explode(' ', $query);
349                                            $sql = "SELECT domain_name
350                                                FROM information_schema.domains
351                                                WHERE domain_name = '$domain';";
352                                            $result = $db->sql_query($sql);
353                                            if (!$db->sql_fetchrow($result))
354                                            {
355                                                $db->sql_query($query);
356                                            }
357                                            $db->sql_freeresult($result);
358                                        }
359                                        else
360                                        {
361                                            $db->sql_query($query);
362                                        }
363
364                                        if (substr($query, 0, 4) == 'COPY')
365                                        {
366                                            while (($sub = $fgetd($fp, "\n", $read, $seek, $eof)) !== '\.')
367                                            {
368                                                if ($sub === false)
369                                                {
370                                                    trigger_error($user->lang['RESTORE_FAILURE'] . adm_back_link($this->u_action), E_USER_WARNING);
371                                                }
372                                                pg_put_line($db->get_db_connect_id(), $sub . "\n");
373                                            }
374                                            pg_put_line($db->get_db_connect_id(), "\\.\n");
375                                            pg_end_copy($db->get_db_connect_id());
376                                        }
377                                    }
378                                break;
379
380                                case 'oracle':
381                                    while (($sql = $fgetd($fp, "/\n", $read, $seek, $eof)) !== false)
382                                    {
383                                        $db->sql_query($sql);
384                                    }
385                                break;
386
387                                case 'mssql_odbc':
388                                case 'mssqlnative':
389                                    while (($sql = $fgetd($fp, "GO\n", $read, $seek, $eof)) !== false)
390                                    {
391                                        $db->sql_query($sql);
392                                    }
393                                break;
394                            }
395
396                            $close($fp);
397
398                            @unlink($temp_file_name);
399
400                            // Purge the cache due to updated data
401                            $cache->purge();
402
403                            $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_RESTORE');
404                            trigger_error($user->lang['RESTORE_SUCCESS'] . adm_back_link($this->u_action));
405                            break;
406                        }
407                        else
408                        {
409                            confirm_box(false, $user->lang['RESTORE_SELECTED_BACKUP'], build_hidden_fields(array('file' => $file)));
410                        }
411
412                    default:
413                        $backup_files = $this->get_file_list($db);
414
415                        if (!empty($backup_files))
416                        {
417                            krsort($backup_files);
418
419                            foreach ($backup_files as $name => $file)
420                            {
421                                $template->assign_block_vars('files', array(
422                                    'FILE'        => sha1($file),
423                                    'NAME'        => $user->format_date($name, 'd-m-Y H:i', true),
424                                    'SUPPORTED'    => true,
425                                ));
426                            }
427                        }
428
429                        $template->assign_vars(array(
430                            'U_ACTION'    => $this->u_action . '&amp;action=submit'
431                        ));
432                    break;
433                }
434            break;
435        }
436    }
437
438    /**
439     * Get backup file from file hash
440     *
441     * @param \phpbb\db\driver\driver_interface $db Database driver
442     * @param string $file_hash Hash of selected file
443     *
444     * @return array Backup file data or empty array if unable to find file
445     */
446    protected function get_backup_file($db, $file_hash)
447    {
448        $backup_data = [];
449
450        $file_list = $this->get_file_list($db);
451        $supported_extensions = $this->get_supported_extensions();
452
453        foreach ($file_list as $file)
454        {
455            preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $file, $matches);
456            if (sha1($file) === $file_hash && in_array($matches[2], $supported_extensions))
457            {
458                $backup_data = [
459                    'file_name' => $file,
460                    'extension' => $matches[2],
461                ];
462                break;
463            }
464        }
465
466        return $backup_data;
467    }
468
469    /**
470     * Get backup file list for directory
471     *
472     * @param \phpbb\db\driver\driver_interface $db Database driver
473     *
474     * @return array List of backup files in specified directory
475     */
476    protected function get_file_list($db)
477    {
478        $supported_extensions = $this->get_supported_extensions();
479
480        $backup_files = [];
481
482        $sql = 'SELECT filename
483            FROM ' . BACKUPS_TABLE;
484        $result = $db->sql_query($sql);
485
486        while ($row = $db->sql_fetchrow($result))
487        {
488            if (preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $row['filename'], $matches))
489            {
490                if (in_array($matches[2], $supported_extensions))
491                {
492                    $backup_files[(int) $matches[1]] = $row['filename'];
493                }
494            }
495        }
496        $db->sql_freeresult($result);
497
498        return $backup_files;
499    }
500
501    /**
502     * Get supported extensions for backup
503     *
504     * @return array List of supported extensions
505     */
506    protected function get_supported_extensions()
507    {
508        $extensions = ['sql'];
509        $available_methods = ['sql.gz' => 'zlib', 'sql.bz2' => 'bz2'];
510
511        foreach ($available_methods as $type => $module)
512        {
513            if (!@extension_loaded($module))
514            {
515                continue;
516            }
517            $extensions[] = $type;
518        }
519
520        return $extensions;
521    }
522}
523
524// 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)
525function get_usable_memory()
526{
527    $val = trim(@ini_get('memory_limit'));
528
529    if (preg_match('/(\\d+)([mkg]?)/i', $val, $regs))
530    {
531        $memory_limit = (int) $regs[1];
532        switch ($regs[2])
533        {
534
535            case 'k':
536            case 'K':
537                $memory_limit *= 1024;
538            break;
539
540            case 'm':
541            case 'M':
542                $memory_limit *= 1048576;
543            break;
544
545            case 'g':
546            case 'G':
547                $memory_limit *= 1073741824;
548            break;
549        }
550
551        // how much memory PHP requires at the start of export (it is really a little less)
552        if ($memory_limit > 6100000)
553        {
554            $memory_limit -= 6100000;
555        }
556
557        // allow us to consume half of the total memory available
558        $memory_limit /= 2;
559    }
560    else
561    {
562        // set the buffer to 1M if we have no clue how much memory PHP will give us :P
563        $memory_limit = 1048576;
564    }
565
566    return $memory_limit;
567}
568
569function sanitize_data_mssql($text)
570{
571    $data = preg_split('/[\n\t\r\b\f]/', $text);
572    preg_match_all('/[\n\t\r\b\f]/', $text, $matches);
573
574    $val = array();
575
576    foreach ($data as $value)
577    {
578        if (strlen($value))
579        {
580            $val[] = "'" . $value . "'";
581        }
582        if (count($matches[0]))
583        {
584            $val[] = 'char(' . ord(array_shift($matches[0])) . ')';
585        }
586    }
587
588    return implode('+', $val);
589}
590
591function sanitize_data_oracle($text)
592{
593//    $data = preg_split('/[\0\n\t\r\b\f\'"\/\\\]/', $text);
594//    preg_match_all('/[\0\n\t\r\b\f\'"\/\\\]/', $text, $matches);
595    $data = preg_split('/[\0\b\f\'\/]/', $text);
596    preg_match_all('/[\0\r\b\f\'\/]/', $text, $matches);
597
598    $val = array();
599
600    foreach ($data as $value)
601    {
602        if (strlen($value))
603        {
604            $val[] = "'" . $value . "'";
605        }
606        if (count($matches[0]))
607        {
608            $val[] = 'chr(' . ord(array_shift($matches[0])) . ')';
609        }
610    }
611
612    return implode('||', $val);
613}
614
615function sanitize_data_generic($text)
616{
617    $data = preg_split('/[\n\t\r\b\f]/', $text);
618    preg_match_all('/[\n\t\r\b\f]/', $text, $matches);
619
620    $val = array();
621
622    foreach ($data as $value)
623    {
624        if (strlen($value))
625        {
626            $val[] = "'" . $value . "'";
627        }
628        if (count($matches[0]))
629        {
630            $val[] = "'" . array_shift($matches[0]) . "'";
631        }
632    }
633
634    return implode('||', $val);
635}
636
637// modified from PHP.net
638function fgetd(&$fp, $delim, $read, $seek, $eof, $buffer = 8192)
639{
640    $record = '';
641    $delim_len = strlen($delim);
642
643    while (!$eof($fp))
644    {
645        $pos = strpos($record, $delim);
646        if ($pos === false)
647        {
648            $record .= $read($fp, $buffer);
649            if ($eof($fp) && ($pos = strpos($record, $delim)) !== false)
650            {
651                $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
652                return substr($record, 0, $pos);
653            }
654        }
655        else
656        {
657            $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
658            return substr($record, 0, $pos);
659        }
660    }
661
662    return false;
663}
664
665function fgetd_seekless(&$fp, $delim, $read, $seek, $eof, $buffer = 8192)
666{
667    static $array = array();
668    static $record = '';
669
670    if (!count($array))
671    {
672        while (!$eof($fp))
673        {
674            if (strpos($record, $delim) !== false)
675            {
676                $array = explode($delim, $record);
677                $record = array_pop($array);
678                break;
679            }
680            else
681            {
682                $record .= $read($fp, $buffer);
683            }
684        }
685        if ($eof($fp) && strpos($record, $delim) !== false)
686        {
687            $array = explode($delim, $record);
688            $record = array_pop($array);
689        }
690    }
691
692    if (count($array))
693    {
694        return array_shift($array);
695    }
696
697    return false;
698}