diff --git a/ChangeLog b/ChangeLog index 1f3144c26..bc02f3731 100755 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,14 @@ $Id$ $Source$ 2003-03-11 Garvin Hicking + * docSQL/*, read_dump.php3, db_details_importdocsql.php3, + libraries/grab_globals.lib.php3, libraries/read_dump.lib.php3: + Improved security of docSQL importer. You now can only access + a subdirectory of phpMyAdmin ('docSQL' proposed) OR upload each + file on its own via File Upload (if $is_upload is true). + Filenames not matching the converter will no more printed out to + maintain security. + * transformation_wrapper.php3 - Remove $err_url, because if a query fails here, the user won't get to see the error string anyways. Can happen if you want to display a blob without having an index diff --git a/db_details_importdocsql.php3 b/db_details_importdocsql.php3 index 0d93c71b1..341eed02a 100644 --- a/db_details_importdocsql.php3 +++ b/db_details_importdocsql.php3 @@ -12,6 +12,7 @@ * Get the values of the variables posted or sent to this script and display * the headers */ +require('./libraries/read_dump.lib.php3'); require('./libraries/grab_globals.lib.php3'); require('./header.inc.php3'); @@ -19,82 +20,95 @@ require('./header.inc.php3'); //all hardcoded strings converted by Robbat2, 15 January 2003 9.34PM require('./libraries/common.lib.php3'); - /** - * Executes import if required + * Imports docSQL files + * + * @param string the basepath + * @param string the filename + * @param string the complete filename + * @param string the content of a file + + * + * @return boolean always true + * + * @global array GLOBAL variables */ -if (isset($do) && $do == 'import') { - // echo '

Starting Import

'; - if (substr($docpath, strlen($docpath) - 2, 1) != '/') { - $docpath = $docpath . '/'; - } - if (is_dir($docpath)) { - // Get relation settings - include('./libraries/relation.lib.php3'); - $cfgRelation = PMA_getRelationsParam(); +function docsql_check($docpath = '', $file = '', $filename = '', $content = 'none') { +global $GLOBALS; - // Do the work - $handle = opendir($docpath); - while ($file = @readdir($handle)) { - $filename = basename($file); - // echo '

Working on file ' . $filename . '

'; - if (strpos(' ' . $filename, '_field_comment.txt')) { - $tab = substr($filename, 0, strlen($filename) - strlen('_field_comment.txt')); - //echo '

Working on Table ' . $_tab . '

'; - $fd = fopen($docpath . $file, 'r'); - if ($fd) { - while (!feof($fd)) { - $line = fgets($fd, 4096); - //echo '

' . $line . '

'; - $inf = explode('|',$line); - if (!empty($inf[1]) && strlen(trim($inf[1])) > 0) { - $qry = 'INSERT INTO ' . PMA_backquote($cfgRelation['column_info']) - . ' (db_name, table_name, column_name, ' . PMA_backquote('comment') . ') ' - . ' VALUES(' - . '\'' . PMA_sqlAddslashes($db) . '\',' - . '\'' . PMA_sqlAddslashes(trim($tab)) . '\',' - . '\'' . PMA_sqlAddslashes(trim($inf[0])) . '\',' - . '\'' . PMA_sqlAddslashes(trim($inf[1])) . '\')'; - if (PMA_query_as_cu($qry)) { - echo '

' . $strAddedColumnComment . ' ' . htmlspecialchars($tab) . '.' . htmlspecialchars($inf[0]) . '

'; - } else { - echo '

' . $strWritingCommentNotPossible . '

'; - } - echo "\n"; - } // end inf[1] exists - if (!empty($inf[2]) && strlen(trim($inf[2])) > 0) { - $for = explode('->', $inf[2]); - $qry = 'INSERT INTO ' . PMA_backquote($cfgRelation['relation']) - . '(master_db, master_table, master_field, foreign_db, foreign_table, foreign_field)' - . ' VALUES(' - . '\'' . PMA_sqlAddslashes($db) . '\', ' - . '\'' . PMA_sqlAddslashes(trim($tab)) . '\', ' - . '\'' . PMA_sqlAddslashes(trim($inf[0])) . '\', ' - . '\'' . PMA_sqlAddslashes($db) . '\', ' - . '\'' . PMA_sqlAddslashes(trim($for[0])) . '\',' - . '\'' . PMA_sqlAddslashes(trim($for[1])) . '\')'; - if (PMA_query_as_cu($qry)) { - echo '

' . $strAddedColumnRelation . ' ' . htmlspecialchars($tab) . '.' . htmlspecialchars($inf[0]) . ' to ' . htmlspecialchars($for) . '

'; - } else { - echo '

' . $strWritingRelationNotPossible . '

'; - } - echo "\n"; - } // end inf[2] exists - } - echo '

' . $strImportFinished . '

' . "\n"; - } else { - echo '

' . $strFileCouldNotBeRead . '

' . "\n"; + if (eregi('^(.*)_field_comment\.(txt|zip|bz2|bzip).*$', $filename)) { + $tab = eregi_replace('^(.*)_field_comment\.(txt|zip|bz2|bzip).*', '\1', $filename); + //echo '

Working on Table ' . $_tab . '

'; + if ($content == 'none') { + $lines = array(); + $fd = fopen($docpath . $file, 'r'); + if ($fd) { + while (!feof($fd)) { + $lines[] = fgets($fd, 4096); } - } else { - echo '

' . sprintf($strIgnoringFile, ' ' . $file) . '

' . "\n"; - } // end working on table - } // end while + } + } else { + $content = str_replace("\r\n", "\n", $content); + $content = str_replace("\r", "\n", $content); + $lines = explode("\n", $content); + } + + if (isset($lines) && is_array($lines) && count($lines) > 0) { + @reset($lines); + while(list($lkey, $line) = each($lines)) { + //echo '

' . $line . '

'; + $inf = explode('|',$line); + if (!empty($inf[1]) && strlen(trim($inf[1])) > 0) { + $qry = 'INSERT INTO ' . PMA_backquote($GLOBALS['cfgRelation']['column_info']) + . ' (db_name, table_name, column_name, ' . PMA_backquote('comment') . ') ' + . ' VALUES(' + . '\'' . PMA_sqlAddslashes($GLOBALS['db']) . '\',' + . '\'' . PMA_sqlAddslashes(trim($tab)) . '\',' + . '\'' . PMA_sqlAddslashes(trim($inf[0])) . '\',' + . '\'' . PMA_sqlAddslashes(trim($inf[1])) . '\')'; + if (PMA_query_as_cu($qry)) { + echo '

' . $GLOBALS['strAddedColumnComment'] . ' ' . htmlspecialchars($tab) . '.' . htmlspecialchars($inf[0]) . '

'; + } else { + echo '

' . $GLOBALS['strWritingCommentNotPossible'] . '

'; + } + echo "\n"; + } // end inf[1] exists + if (!empty($inf[2]) && strlen(trim($inf[2])) > 0) { + $for = explode('->', $inf[2]); + $qry = 'INSERT INTO ' . PMA_backquote($GLOBALS['cfgRelation']['relation']) + . '(master_db, master_table, master_field, foreign_db, foreign_table, foreign_field)' + . ' VALUES(' + . '\'' . PMA_sqlAddslashes($GLOBALS['db']) . '\', ' + . '\'' . PMA_sqlAddslashes(trim($tab)) . '\', ' + . '\'' . PMA_sqlAddslashes(trim($inf[0])) . '\', ' + . '\'' . PMA_sqlAddslashes($GLOBALS['db']) . '\', ' + . '\'' . PMA_sqlAddslashes(trim($for[0])) . '\',' + . '\'' . PMA_sqlAddslashes(trim($for[1])) . '\')'; + if (PMA_query_as_cu($qry)) { + echo '

' . $GLOBALS['strAddedColumnRelation'] . ' ' . htmlspecialchars($tab) . '.' . htmlspecialchars($inf[0]) . ' to ' . htmlspecialchars($inf[2]) . '

'; + } else { + echo '

' . $GLOBALS['strWritingRelationNotPossible'] . '

'; + } + echo "\n"; + } // end inf[2] exists + } + echo '

' . $GLOBALS['strImportFinished'] . '

' . "\n"; + } else { + echo '

' . $GLOBALS['strFileCouldNotBeRead'] . '

' . "\n"; + } + + return 1; } else { - echo $strThisNotDirectory . "\n"; - } + if ($content != '') { + echo '

' . sprintf($GLOBALS['strIgnoringFile'], ' ' . $file) . '

' . "\n"; + } else { + // garvin: disabled. Shouldn't impose ANY non-submitted files ever. + echo '

' . sprintf($GLOBALS['strIgnoringFile'], ' ' . '') . '

' . "\n"; + } + return 0; + } // end working on table } - /** * Try to get the "$DOCUMENT_ROOT" variable whatever is the register_globals * value @@ -116,23 +130,167 @@ if (empty($DOCUMENT_ROOT)) { $DOCUMENT_ROOT = getenv('DOCUMENT_ROOT'); } else { - $DOCUMENT_ROOT = ''; + $DOCUMENT_ROOT = '.'; } } // end if +/** + * Executes import if required + */ +if (isset($do) && $do == 'import') { + $orig_docpath = $docpath; + + if (empty($sql_file)) { + $sql_file = 'none'; + } + + // Get relation settings + include('./libraries/relation.lib.php3'); + $cfgRelation = PMA_getRelationsParam(); + + // Gets the query from a file if required + if ($sql_file != 'none') { + if (file_exists($sql_file) + && is_uploaded_file($sql_file)) { + + $open_basedir = ''; + if (PMA_PHP_INT_VERSION >= 40000) { + $open_basedir = @ini_get('open_basedir'); + } + if (empty($open_basedir)) { + $open_basedir = @get_cfg_var('open_basedir'); + } + + // If we are on a server with open_basedir, we must move the file + // before opening it. The doc explains how to create the "./tmp" + // directory + + if (!empty($open_basedir)) { + + $tmp_subdir = (PMA_IS_WINDOWS ? '.\\tmp\\' : './tmp/'); + + // function is_writeable() is valid on PHP3 and 4 + if (!is_writeable($tmp_subdir)) { + // if we cannot move the file, let PHP report the error + error_reporting(E_ALL); + $docsql_text = PMA_readFile($sql_file, $sql_file_compression); + } + else { + $sql_file_new = $tmp_subdir . basename($sql_file); + if (PMA_PHP_INT_VERSION < 40003) { + copy($sql_file, $sql_file_new); + } else { + move_uploaded_file($sql_file, $sql_file_new); + } + $docsql_text = PMA_readFile($sql_file_new, $sql_file_compression); + unlink($sql_file_new); + } + } + else { + // read from the normal upload dir + $docsql_text = PMA_readFile($sql_file, $sql_file_compression); + } + + // Convert the file's charset if necessary + if ($cfg['AllowAnywhereRecoding'] && $allow_recoding + && isset($charset_of_file) && $charset_of_file != $charset) { + $docsql_text = PMA_convert_string($charset_of_file, $charset, $docsql_text); + } + + if (!isset($docsql_text) || $docsql_text == FALSE || $docsql_text == '') { + echo '

' . $GLOBALS['strFileCouldNotBeRead'] . '

' . "\n"; + } else { + docsql_check('', $sql_file_name, $sql_file_name, $docsql_text); + } + } // end uploaded file stuff + } else { + + // echo '

Starting Import

'; + $docpath = $DOCUMENT_ROOT . dirname($PHP_SELF) . '/' . $docpath; + if (substr($docpath, strlen($docpath) - 2, 1) != '/') { + $docpath = $docpath . '/'; + } + + $matched_files = 0; + + if (is_dir($docpath)) { + // Do the work + $handle = opendir($docpath); + while ($file = @readdir($handle)) { + $filename = basename($file); + // echo '

Working on file ' . $filename . '

'; + $matched_files += docsql_check($docpath, $file, $filename); + } // end while + } else { + echo '

' .$docpath . ': ' . $strThisNotDirectory . "

\n"; + } + } +} + /** * Displays the form */ ?> -
+> :

-      + / +
'; + echo ' ' . $strOr . ' ' . $strLocationTextfile . ' :
' . "\n"; + ?> +
+
+ ' . "\n" + . ' ' . "\n"; + while ($temp_charset = next($cfg['AvailableCharsets'])) { + echo ' ' . "\n"; + } // end while + echo '
' . "\n" . ' '; + } // end if + $is_gzip = ($cfg['GZipDump'] && @function_exists('gzopen')); + $is_bzip = ($cfg['BZipDump'] && @function_exists('bzdecompress')); + if ($is_bzip || $is_gzip) { + echo ' ' . $strCompression . ':' . "\n" + . ' ' . "\n" + . '    ' . "\n"; + if ($is_gzip) { + echo ' ' . "\n" + . '    ' . "\n"; + } + if ($is_bzip) { + echo ' ' . "\n" + . '    ' . "\n"; + } + } else { + echo ' ' . "\n"; + } + ?> +
+ +
 
diff --git a/docSQL/README b/docSQL/README new file mode 100755 index 000000000..054d7e88a --- /dev/null +++ b/docSQL/README @@ -0,0 +1,9 @@ +$Id$ + +phpMyAdmin - docSQL +=================== + + This directory is empty by default. You can put your docSQL files + here, if you want them to be imported via phpMyAdmin. + + If you do not know, what docSQL is, just leave this directory empty. diff --git a/libraries/grab_globals.lib.php3 b/libraries/grab_globals.lib.php3 index 28666fcb9..c24148862 100644 --- a/libraries/grab_globals.lib.php3 +++ b/libraries/grab_globals.lib.php3 @@ -47,10 +47,12 @@ if (!defined('PMA_GRAB_GLOBALS_INCLUDED')) { if (!empty($_FILES)) { while (list($name, $value) = each($_FILES)) { $$name = $value['tmp_name']; + ${$name . '_name'} = $value['name']; } } else if (!empty($HTTP_POST_FILES)) { while (list($name, $value) = each($HTTP_POST_FILES)) { $$name = $value['tmp_name']; + ${$name . '_name'} = $value['name']; } } // end if diff --git a/libraries/read_dump.lib.php3 b/libraries/read_dump.lib.php3 new file mode 100644 index 000000000..bbd9f90f4 --- /dev/null +++ b/libraries/read_dump.lib.php3 @@ -0,0 +1,196 @@ + add the current substring to the + // returned array + if (!$i) { + $ret[] = $sql; + return TRUE; + } + // Backquotes or no backslashes before quotes: it's indeed the + // end of the string -> exit the loop + else if ($string_start == '`' || $sql[$i-1] != '\\') { + $string_start = ''; + $in_string = FALSE; + break; + } + // one or more Backslashes before the presumed end of string... + else { + // ... first checks for escaped backslashes + $j = 2; + $escaped_backslash = FALSE; + while ($i-$j > 0 && $sql[$i-$j] == '\\') { + $escaped_backslash = !$escaped_backslash; + $j++; + } + // ... if escaped backslashes: it's really the end of the + // string -> exit the loop + if ($escaped_backslash) { + $string_start = ''; + $in_string = FALSE; + break; + } + // ... else loop + else { + $i++; + } + } // end if...elseif...else + } // end for + } // end if (in string) + + // We are not in a string, first check for delimiter... + else if ($char == ';') { + // if delimiter found, add the parsed part to the returned array + $ret[] = substr($sql, 0, $i); + $sql = ltrim(substr($sql, min($i + 1, $sql_len))); + $sql_len = strlen($sql); + if ($sql_len) { + $i = -1; + } else { + // The submited statement(s) end(s) here + return TRUE; + } + } // end else if (is delimiter) + + // ... then check for start of a string,... + else if (($char == '"') || ($char == '\'') || ($char == '`')) { + $in_string = TRUE; + $string_start = $char; + } // end else if (is start of string) + + // ... for start of a comment (and remove this comment if found)... + else if ($char == '#' + || ($char == ' ' && $i > 1 && $sql[$i-2] . $sql[$i-1] == '--')) { + // starting position of the comment depends on the comment type + $start_of_comment = (($sql[$i] == '#') ? $i : $i-2); + // if no "\n" exits in the remaining string, checks for "\r" + // (Mac eol style) + $end_of_comment = (strpos(' ' . $sql, "\012", $i+2)) + ? strpos(' ' . $sql, "\012", $i+2) + : strpos(' ' . $sql, "\015", $i+2); + if (!$end_of_comment) { + // no eol found after '#', add the parsed part to the returned + // array if required and exit + if ($start_of_comment > 0) { + $ret[] = trim(substr($sql, 0, $start_of_comment)); + } + return TRUE; + } else { + $sql = substr($sql, 0, $start_of_comment) + . ltrim(substr($sql, $end_of_comment)); + $sql_len = strlen($sql); + $i--; + } // end if...else + } // end else if (is comment) + + // ... and finally disactivate the "/*!...*/" syntax if MySQL < 3.22.07 + else if ($release < 32270 + && ($char == '!' && $i > 1 && $sql[$i-2] . $sql[$i-1] == '/*')) { + $sql[$i] = ' '; + } // end else if + + // loic1: send a fake header each 30 sec. to bypass browser timeout + $time1 = time(); + if ($time1 >= $time0 + 30) { + $time0 = $time1; + header('X-pmaPing: Pong'); + } // end if + } // end for + + // add any rest to the returned array + if (!empty($sql) && ereg('[^[:space:]]+', $sql)) { + $ret[] = $sql; + } + + return TRUE; + } // end of the 'PMA_splitSqlFile()' function + + + /** + * Reads (and decompresses) a (compressed) file into a string + * + * @param string the path to the file + * @param string the MIME type of the file + * + * @global array the phpMyAdmin configuration + * + * @return string the content of the file or + * boolean FALSE in case of an error. + */ + function PMA_readFile($path, $mime = 'text/plain') { + global $cfg; + + switch ($mime) { + case 'text/plain': + $file = fopen($path, 'rb'); + $content = fread($file, filesize($path)); + fclose($file); + break; + case 'application/x-gzip': + if ($cfg['GZipDump'] && @function_exists('gzopen')) { + $file = gzopen($path, 'rb'); + $content = ''; + while (!gzeof($file)) { + $content .= gzgetc($file); + } + gzclose($file); + } else { + return FALSE; + } + break; + case 'application/x-bzip': + if ($cfg['BZipDump'] && @function_exists('bzdecompress')) { + $file = fopen($path, 'rb'); + $content = fread($file, filesize($path)); + fclose($file); + $content = bzdecompress($content); + } else { + return FALSE; + } + break; + default: + return FALSE; + } + if (!file_exists($path)) { + return FALSE; + } + return $content; + } +} // $__PMA_READ_DUMP_LIB__ + +?> \ No newline at end of file diff --git a/read_dump.php3 b/read_dump.php3 index f906c9dd7..ed658302b 100644 --- a/read_dump.php3 +++ b/read_dump.php3 @@ -2,198 +2,10 @@ /* $Id$ */ // vim: expandtab sw=4 ts=4 sts=4: - -/** - * Removes comment lines and splits up large sql files into individual queries - * - * Last revision: September 23, 2001 - gandon - * - * @param array the splitted sql commands - * @param string the sql commands - * @param integer the MySQL release number (because certains php3 versions - * can't get the value of a constant from within a function) - * - * @return boolean always true - * - * @access public - */ -function PMA_splitSqlFile(&$ret, $sql, $release) -{ - $sql = trim($sql); - $sql_len = strlen($sql); - $char = ''; - $string_start = ''; - $in_string = FALSE; - $time0 = time(); - - for ($i = 0; $i < $sql_len; ++$i) { - $char = $sql[$i]; - - // We are in a string, check for not escaped end of strings except for - // backquotes that can't be escaped - if ($in_string) { - for (;;) { - $i = strpos($sql, $string_start, $i); - // No end of string found -> add the current substring to the - // returned array - if (!$i) { - $ret[] = $sql; - return TRUE; - } - // Backquotes or no backslashes before quotes: it's indeed the - // end of the string -> exit the loop - else if ($string_start == '`' || $sql[$i-1] != '\\') { - $string_start = ''; - $in_string = FALSE; - break; - } - // one or more Backslashes before the presumed end of string... - else { - // ... first checks for escaped backslashes - $j = 2; - $escaped_backslash = FALSE; - while ($i-$j > 0 && $sql[$i-$j] == '\\') { - $escaped_backslash = !$escaped_backslash; - $j++; - } - // ... if escaped backslashes: it's really the end of the - // string -> exit the loop - if ($escaped_backslash) { - $string_start = ''; - $in_string = FALSE; - break; - } - // ... else loop - else { - $i++; - } - } // end if...elseif...else - } // end for - } // end if (in string) - - // We are not in a string, first check for delimiter... - else if ($char == ';') { - // if delimiter found, add the parsed part to the returned array - $ret[] = substr($sql, 0, $i); - $sql = ltrim(substr($sql, min($i + 1, $sql_len))); - $sql_len = strlen($sql); - if ($sql_len) { - $i = -1; - } else { - // The submited statement(s) end(s) here - return TRUE; - } - } // end else if (is delimiter) - - // ... then check for start of a string,... - else if (($char == '"') || ($char == '\'') || ($char == '`')) { - $in_string = TRUE; - $string_start = $char; - } // end else if (is start of string) - - // ... for start of a comment (and remove this comment if found)... - else if ($char == '#' - || ($char == ' ' && $i > 1 && $sql[$i-2] . $sql[$i-1] == '--')) { - // starting position of the comment depends on the comment type - $start_of_comment = (($sql[$i] == '#') ? $i : $i-2); - // if no "\n" exits in the remaining string, checks for "\r" - // (Mac eol style) - $end_of_comment = (strpos(' ' . $sql, "\012", $i+2)) - ? strpos(' ' . $sql, "\012", $i+2) - : strpos(' ' . $sql, "\015", $i+2); - if (!$end_of_comment) { - // no eol found after '#', add the parsed part to the returned - // array if required and exit - if ($start_of_comment > 0) { - $ret[] = trim(substr($sql, 0, $start_of_comment)); - } - return TRUE; - } else { - $sql = substr($sql, 0, $start_of_comment) - . ltrim(substr($sql, $end_of_comment)); - $sql_len = strlen($sql); - $i--; - } // end if...else - } // end else if (is comment) - - // ... and finally disactivate the "/*!...*/" syntax if MySQL < 3.22.07 - else if ($release < 32270 - && ($char == '!' && $i > 1 && $sql[$i-2] . $sql[$i-1] == '/*')) { - $sql[$i] = ' '; - } // end else if - - // loic1: send a fake header each 30 sec. to bypass browser timeout - $time1 = time(); - if ($time1 >= $time0 + 30) { - $time0 = $time1; - header('X-pmaPing: Pong'); - } // end if - } // end for - - // add any rest to the returned array - if (!empty($sql) && ereg('[^[:space:]]+', $sql)) { - $ret[] = $sql; - } - - return TRUE; -} // end of the 'PMA_splitSqlFile()' function - - -/** - * Reads (and decompresses) a (compressed) file into a string - * - * @param string the path to the file - * @param string the MIME type of the file - * - * @global array the phpMyAdmin configuration - * - * @return string the content of the file or - * boolean FALSE in case of an error. - */ -function PMA_readFile($path, $mime = 'text/plain') { - global $cfg; - - switch ($mime) { - case 'text/plain': - $file = fopen($path, 'rb'); - $content = fread($file, filesize($path)); - fclose($file); - break; - case 'application/x-gzip': - if ($cfg['GZipDump'] && @function_exists('gzopen')) { - $file = gzopen($path, 'rb'); - $content = ''; - while (!gzeof($file)) { - $content .= gzgetc($file); - } - gzclose($file); - } else { - return FALSE; - } - break; - case 'application/x-bzip': - if ($cfg['BZipDump'] && @function_exists('bzdecompress')) { - $file = fopen($path, 'rb'); - $content = fread($file, filesize($path)); - fclose($file); - $content = bzdecompress($content); - } else { - return FALSE; - } - break; - default: - return FALSE; - } - if (!file_exists($path)) { - return FALSE; - } - return $content; -} - - /** * Gets some core libraries */ +require('./libraries/read_dump.lib.php3'); require('./libraries/grab_globals.lib.php3'); require('./libraries/common.lib.php3');