Can with this class prevent or detect possible SQL attacks? - php

I just wrote a simple PHP class that i think it will help me prevent or detect possible SQL attacks. I am not sure if that really works and thats why im posting it here to read your ideas and suggestions.
So lets start from the classic config.php file that creates a connection with the DB and selects a specific Database. I turned it into a class and it returns the connection link in the main page. Lets see:
<?php
class mysql_init
{
public function initDb(){
$con = mysqli_connect("localhost","user","password");
mysqli_select_db($con,"geneticDb");
return $con;
}
}
?>
So after this lets see the code of the main page safe.php. It has a simple example of fetching data from the DB through an Id that user gives with GET request.
<?php
require_once('filter.php');
require_once('config.php');
$connection = new mysql_init();
$con = $connection->initDb();
$safe = new filter($con,'GET');
$n_id = $_GET['noteId'];
$data = mysqli_query($con,"SELECT noteType FROM notes WHERE noteId = $n_id ");
if($data){
$row = mysqli_fetch_array($data, MYSQLI_BOTH);
echo $row["noteType"];
}
?>
As you can see i am including also and the filter.php which is a class that scans the GET or POST requests that user sends. If it will find something curious it takes the thread id of the session with the Database and shut it down. The truth is that is not finished and i mean that i will put it some futures to save some logs (of the attacker) to the database, and redirect him/her to another page informing him/her for the record.
<?php
class filter
{
private $_activity = 0;
public function __construct($sql_connection,$method){
switch ($method) {
case 'GET':
$this->check_GET($sql_connection);
break;
case 'POST':
$this->check_POST($sql_connection);
break;
case 'ALL':
$this->check_POST($sql_connection);
$this->check_GET($sql_connection);
break;
default:
# code...
break;
}
}
private function check_GET($con){
if($_SERVER['REQUEST_METHOD'] == 'GET')
{
echo $_SERVER['REQUEST_URI']."<br>".$_SERVER['SCRIPT_NAME']."<br><br>";
foreach($_GET as $index => $value)
{
if(preg_match('/\s/', $value)) # no whitespaces
$this->_activity = 1;
if(preg_match('/[\'"]/', $value)) # no quotes
$this->_activity = 1;
if(preg_match('/[\/\\\\]/', $value)) # no slashes
$this->_activity = 1;
if(preg_match('/(and|or|null|not|if)/i', $value)) # no sqli boolean keywords
$this->_activity = 1;
if(preg_match('/(union|select|from|where)/i', $value)) # no sqli select keywords
$this->_activity = 1;
if(preg_match('/(group|order|having|limit)/i', $value)) # no sqli select keywords
$this->_activity = 1;
if(preg_match('/(into|file|case)/i', $value)) # no sqli operators
$this->_activity = 1;
if(preg_match('/(;|--|#|\/\*)/', $value)) # no sqli comments
$this->_activity = 1;
if(preg_match('/(=|&|\|)/', $value)) # no boolean operators
$this->_activity = 1;
if(isset($this->_activity) && $this->_activity == 1){
echo "Something detected => ".$index." : ".$value."<br>";
$thread_id = mysqli_thread_id($con);
mysqli_kill($con, $thread_id);
$this->_activity = 0;
}
}
}
}
private function check_POST($con){
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
echo $_SERVER['REQUEST_URI']."<br>".$_SERVER['SCRIPT_NAME']."<br><br>";
foreach($_POST as $index => $value)
{
if(preg_match('/\s/', $value)) # no whitespaces
$this->_activity = 1;
if(preg_match('/[\'"]/', $value)) # no quotes
$this->_activity = 1;
if(preg_match('/[\/\\\\]/', $value)) # no slashes
$this->_activity = 1;
if(preg_match('/(and|or|null|not|if)/i', $value)) # no sqli boolean keywords
$this->_activity = 1;
if(preg_match('/(union|select|from|where)/i', $value)) # no sqli select keywords
$this->_activity = 1;
if(preg_match('/(group|order|having|limit)/i', $value)) # no sqli select keywords
$this->_activity = 1;
if(preg_match('/(into|file|case)/i', $value)) # no sqli operators
$this->_activity = 1;
if(preg_match('/(;|--|#|\/\*)/', $value)) # no sqli comments
$this->_activity = 1;
if(preg_match('/(=|&|\|)/', $value)) # no boolean operators
$this->_activity = 1;
if(isset($this->_activity) && $this->_activity == 1){
echo "Something detected => ".$index." : ".$value."<br>";
$thread_id = mysqli_thread_id($con);
mysqli_kill($con, $thread_id);
$this->_activity = 0;
}
}
}
}
}
?>
So thats all for now. Do you think that this code is effective, or somehow it can be bypassed ?
Thank you.

Can with this class prevent or detect possible SQL attacks?
No.
It is inviable for the real life too.
Imagine a similar approach were used on this very site of Stack Overflow. Were you able to post your question, full of "dangerous" characters?

Related

How to get a list of SQL query paramerters for PHP OCI?

My application is executing user-defined SQL statements that contain query parameters. To detect the parameter names that should be passed to oci_bind_by_name I use a simple reg-ex pattern like /:\w+/ but this fails for string literals and comments contained in the SQL statement.
BEGIN
/* some unused :param here */
SELECT 'some other :param there' FROM foo;
END;
Handling string literal detection and comment by more reg-ex patterns seems like a bad idea when thinking about even more nasty examples like:
BEGIN
SELECT '/* some comment :literals --' FROM foo;
-- some more comment :literals */
END;
Is there some way to get the required query parameter names for binding using OCI8 functions? What other possibilities do exist without falling back to manually parsing SQL in user code?
My code below is not a great way to solve this problem. Before you use that code, keep looking for a more official solution.
It appears that OCI does have functionality to dynamically retrieve bind names, through the function OCIStmtGetBindInfo. However, it also looks like that function is not available in the default PHP functions. Maybe there are other, more advanced ways of connecting PHP to Oracle that supply the necessary function, but I don't know enough about OCI or PHP to find them.
If you're ready for a not-so-great solution, you can use my open source program plsql_lexer to find the bind variable names. The lexer breaks SQL statements into small tokens, and handles difficult syntax issues like comments and strings. The results should be much more accurate than using a few regular expressions.
The downside is that the program is not a full parser, and you have to deal with the primitive tokens. In this case, it's relatively easy to find 99.9999% of the bind variables with a single SQL statement. After installing the program, put your SQL into the middle of the following SELECT statement:
--Find bind variables.
--(Words or numerics that were immediately preceded (excluding whitespace) by a colon.)
select to_char(value) bind_variable_name
from
(
--Get previous token.
select type, value, first_char_position,
lag(to_char(type)) over (order by first_char_position) previous_type
from
(
--Convert to tokens, ignore whitespace.
select type, value, first_char_position
from table(plsql_lexer.lex(
q'[
--Here's the actual SQL statement you care about.
--/*:fake_bind1*/
select 1 a
from dual
where 1 = : real_bind_1 and :real_bind_2 = ':fake_bind_2'
]'))
where type not in ('whitespace')
order by first_char_position
)
)
where type in ('numeric', 'word')
and previous_type = ':'
order by first_char_position;
BIND_VARIABLE_NAME
------------------
real_bind_1
real_bind_2
There may still be some weird cases this code doesn't handle. For example, a bind variable can be a quoted identifier, you may need to handle the double quotes. And the above code doesn't handle indicators. On the other hand, I have literally never seen either of those features used, so it may not matter to you. Test thoroughly.
Finally I wrote some small state machine for parsing SQL statement bind parameters and put it into a helper class to not conflict with other globals:
class SqlBindNames {
private static function isLineBreak($ch) {
return (($ch === "\r") || ($ch === "\n"));
}
private static function isIdentChar($ch) {
return (($ch >= 'a') && ($ch <= 'z')) ||
(($ch >= 'A') && ($ch <= 'Z')) ||
(($ch >= '0') && ($ch <= '9')) ||
($ch === '_');
}
private const QUOTE_SINGLE_CHR = '\'';
private const QUOTE_DOUBLE_CHR = '"';
private const COMMENT_LINE_STR = "--";
private const COMMENT_BEGIN_STR = "/*";
private const COMMENT_END_STR = "*/";
private const BIND_START_CHR = ':';
private const MODE_NORMAL = 0;
private const MODE_QUOTE_SINGLE = 1;
private const MODE_QUOTE_DOUBLE = 2;
private const MODE_COMMENT_LINE = 3;
private const MODE_COMMENT_MULTI = 4;
private const MODE_BIND_VARNAME = 5;
public static function getSqlBindNames(string $sql, bool $unique = true) {
$mode = self::MODE_NORMAL;
$names = array();
$namesIndex = array();
$len = strlen($sql);
$i = 0;
while ($i < $len) {
$curr = $sql[$i];
if ($i < $len - 1) {
$next = $sql[$i + 1];
} else {
$next = "\0";
}
$nextMode = $mode;
if ($mode === self::MODE_NORMAL) {
if ($curr === self::QUOTE_SINGLE_CHR) {
$nextMode = self::MODE_QUOTE_SINGLE;
} else if ($curr === self::QUOTE_DOUBLE_CHR) {
$nextMode = self::MODE_QUOTE_DOUBLE;
} else if (($curr === self::COMMENT_LINE_STR[0]) && ($next === self::COMMENT_LINE_STR[1])) {
$i += 1;
$nextMode = self::MODE_COMMENT_LINE;
} else if (($curr === self::COMMENT_BEGIN_STR[0]) && ($next === self::COMMENT_BEGIN_STR[1])) {
$i += 1;
$nextMode = self::MODE_COMMENT_MULTI;
} else if (($curr === self::BIND_START_CHR) && self::isIdentChar($next)) {
$bindName = "";
$nextMode = self::MODE_BIND_VARNAME;
}
} else if (($mode === self::MODE_QUOTE_SINGLE) && ($curr === self::QUOTE_SINGLE_CHR)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_QUOTE_DOUBLE) && ($curr === self::QUOTE_DOUBLE_CHR)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_COMMENT_LINE) && self::isLineBreak($curr)) {
$nextMode = self::MODE_NORMAL;
} else if (($mode === self::MODE_COMMENT_MULTI) && ($curr === self::COMMENT_END_STR[0]) && ($next === self::COMMENT_END_STR[1])) {
$i += 1;
$nextMode = self::MODE_NORMAL;
} else if ($mode === self::MODE_BIND_VARNAME) {
if (self::isIdentChar($curr)) {
$bindName = $bindName . $curr;
}
if (!self::isIdentChar($next)) {
/* found new bind param */
if (!$unique || !in_array(strtolower($bindName), $namesIndex)) {
array_push($namesIndex, strtolower($bindName));
array_push($names, $bindName);
}
$nextMode = self::MODE_NORMAL;
}
}
$i += 1;
$mode = $nextMode;
}
return $names;
}
}
It seems to work, improvements are welcome!

regex php url title escaping

Hello I'm making a website and I have issues with htaccess clean url redirects when a user adds amps or slashes as a title for the article he is submiting.I decided thath I would like to allow only specific characters like alpanumeric, -, _, #,[,]. no quotes or double quotes etc etc...
I cant seem to make the regexp work I have not great esperience with regex and instead of using string replaces I thought I should ask.
Also any other proposals regarding this matter will be greatly appreciated
In my htaccess I have the following setup:
RewriteRule ^([^/]*)(.*)$ /index.php?page=$1&request=$2
and in my index I have:
include 'db.php';
include 'generic.php';
$pages = array('','main','events','news','rss','search','add-event');
if(isset($_GET['page']) && in_array($_GET['page'], $pages))
{
$event_category = null;
$event_title = null;
$search_string = null;
$rss_category = null;
$events_date = null;
$search_page = null;
validate_evented_page(trim(urldecode($_GET['page'])));
}
else
{
evented_error_page(404);
}
function evented_error_page($err)
{
include('errorpages/error.php');
}
function validate_evented_page($p)
{
$page = strtolower($p);
global $event_category;
global $event_title;
global $search_string;
global $rss_category;
global $events_date;
global $search_page;
if($page == 'main' || strlen($p) == 0)
{
include 'main.php';
}
else if($page == 'events')
{
$params = explode_url($_GET['request']);
if(count($params) == 0)
{
$events_date = "'';";
include 'allevents.php';
}
else if(count($params) == 1)
{
if(check_date($params[0]))
{
$date_split = explode('-',$params[0]);
$events_date = "'".date("m/d/Y" , mktime(0, 0, 0, date('m'), $date_split[1], date('Y')))."';";
}
else
{
$events_date = "'';";
}
include 'allevents.php';
}
else if(count($params) == 2)
{
$event_category = trim(urldecode($params[0]));
$event_title = trim(urldecode($params[1]));
include 'event.php';
}
else
{
evented_error_page(404);
}
}
}
the following url:
/events/Drum+%26+Bass/Innersense+presents+ETHOS_v04-0
gives $_GET['request'] = /Drum
when it should have been Drum & Bass (in the database I have stored this category as "Drum & Bass").
You need to tell mod_rewrite to encode the & that gets grouped in the backreferene using the B flag:
RewriteRule ^([^/]*)(.*)$ /index.php?page=$1&request=$2 [B,L]
The URI gets decoded when it gets sent through the rewrite engine. So the %26 gets decoded to &, the B flag ensures it gets re-encoded as the backreference.

isset $_post issue with mysql query

I'm just working on the Backend of a project an have a small problem with this snippet
if (isset($_POST['id'])) {
$cat_delete = "DELETE FROM category WHERE categoryid='".$_POST['id']."' ";
$cat_delete_ex = mysql_query($cat_delete);}`
But if the id is set with post, nothing happens.
The mysql query is working when I delete the
if (isset($_POST['id']))
anyone have an idea ?
Well I am not sure if your method is safe or not, but I would do it like this, might even throw in a regex to check for just numbers if the id is numeric:
EDIT: I made a revision, since you are dealing with an ID, I will assume the ID is numeric only, so instead of escaping it, I just will strip out everything but numbers. This may be a better fit for your situation. I also converted the function to a class so you will be able to reuse the script for several types of sanitizing strings. Maybe its because I am an overachiever too, I don't know. ADD, OCD, etc. Blame it on that :)
$postID = isset($_POST['id']) ? sanitize::ID($_POST['id']) : '';
if (sanitize::email("test#example.com")){
echo "Real email";
} else {
echo "Fake email";
}
if ($postID != ''){
$cat_delete = "DELETE FROM category WHERE categoryid='".$postID."' ";
$cat_delete_ex = mysql_query($cat_delete);
}
class sanitize{
function ID($string){
$string = preg_replace('/[^0-9,]|,[0-9]*$/','',$string);
return $string;
}
# I added another sanitize function so you can see what you can do
# with it. Add phone numbers, domain names, etc... Each one could
# be called with sanitize::{FUNCTION}
function email($string){
if (!ereg("^[^#]{1,64}#[^#]{1,255}$", $string)) {
return false;
}
$email_array = explode("#", $string);
$local_array = explode(".", $email_array[0]);
for ($i = 0; $i < sizeof($local_array); $i++) {
if (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$",$local_array[$i])) return false;
}
if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) {
$domain_array = explode(".", $email_array[1]);
if (sizeof($domain_array) < 2) return false;
for ($i = 0; $i < sizeof($domain_array); $i++) {
if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i])) return false;
}
}
return true;
}
}
are you sure you are using post for the id?(asking because is the right way, but i have one too many times
<form action="action.php?id=hereistheid"
which will bring the id in the $_GET not $_POST.
next the checking
$id=(int)$_POST['id'];
if($id)
{
//do smth
}

How to clean and simplify this code?

After thinking about This Question and giving an answer to it I wanted to do more about that to train myself.
So I wrote a function which will calc the length of an given function. Th given php-file has to start at the beginning of the needed function.
Example: If the function is in a big phpfile with lots of functions, like
/* lots of functions */
function f_interesting($arg) {
/* function */
}
/* lots of other functions */
then $part3 of my function will require to begin like that (after the starting-{ of the interesting function):
/* function */
}
/* lots of other functions */
Now that's not the problem, but I would like to know if there are an cleaner or simplier ways to do this. Here's my function: (I already cleaned a lot of testing-echo-commands)
(The idea behind it is explained here)
function f_analysis ($part3) {
if(isset($part3)) {
$char_array = str_split($part3); //get array of chars
$end_key = false; //length of function
$depth = 0; //How much of unclosed '{'
$in_sstr = false; //is next char inside in ''-String?
$in_dstr = false; //is nect char inside an ""-String?
$in_sl_comment = false; //inside an //-comment?
$in_ml_comment = false; //inside an /* */-comment?
$may_comment = false; //was the last char an '/' which can start a comment?
$may_ml_comment_end = false; //was the last char an '*' which may end a /**/-comment?
foreach($char_array as $key=>$char) {
if($in_sstr) {
if ($char == "'") {
$in_sstr = false;
}
}
else if($in_dstr) {
if($char == '"') {
$in_dstr = false;
}
}
else if($in_sl_comment) {
if($char == "\n") {
$in_sl_comment = false;
}
}
else if($in_ml_comment) {
if($may_ml_comment_end) {
$may_ml_comment_end = false;
if($char == '/') {
$in_ml_comment = false;
}
}
if($char == '*') {
$may_ml_comment_end = true;
}
}
else if ($may_comment) {
if($char == '/') {
$in_sl_comment = true;
}
else if($char == '*') {
$in_ml_comment = true;
}
$may_comment = false;
}
else {
switch ($char) {
case '{':
$depth++;
break;
case '}':
$depth--;
break;
case '/':
$may_comment = true;
break;
case '"':
$in_dstr = true;
break;
case "'":
$in_sstr = true;
break;
}
}
if($depth < 0) {
$last_key = $key;
break;
}
}
} else echo '<br>$part3 of f_analysis not set!';
return ($last_key===false) ? false : $last_key+1; //will be false or the length of the function
}
Tokenizer (Example) - Learn it, love it.
You could probably reduce the number of state variables a little, but truthfully... yes, it will be messy code. I would probably get rid of $may_ml_comment_end and peek ahead for the next character when I encounter an asterisk, for example. You will need to rewrite your foreach loop to a regular for loop be able to do that without creating a bigger mess though.
PS: I don't see you handling the escape character yet. Without the above approach, that would introduce another boolean variable.
Another problem with your current code is that characters immediately following a / don't get interpreted as they should. However unlikely
echo 5/'2'; // NB: no space in between
is valid in PHP and would break your parser.

Loading .sql files from within PHP

I'm creating an installation script for an application that I'm developing and need to create databases dynamically from within PHP. I've got it to create the database but now I need to load in several .sql files. I had planned to open the file and mysql_query it a line at a time - until I looked at the schema files and realised they aren't just one query per line.
So, how do I load an sql file from within PHP (as phpMyAdmin does with its import command)?
$db = new PDO($dsn, $user, $password);
$sql = file_get_contents('file.sql');
$qr = $db->exec($sql);
phpBB uses a few functions to parse their files. They are rather well-commented (what an exception!) so you can easily know what they do (I got this solution from http://www.frihost.com/forums/vt-8194.html). here is the solution an I've used it a lot:
<?php
ini_set('memory_limit', '5120M');
set_time_limit ( 0 );
/***************************************************************************
* sql_parse.php
* -------------------
* begin : Thu May 31, 2001
* copyright : (C) 2001 The phpBB Group
* email : support#phpbb.com
*
* $Id: sql_parse.php,v 1.8 2002/03/18 23:53:12 psotfx Exp $
*
****************************************************************************/
/***************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/
/***************************************************************************
*
* These functions are mainly for use in the db_utilities under the admin
* however in order to make these functions available elsewhere, specifically
* in the installation phase of phpBB I have seperated out a couple of
* functions into this file. JLH
*
\***************************************************************************/
//
// remove_comments will strip the sql comment lines out of an uploaded sql file
// specifically for mssql and postgres type files in the install....
//
function remove_comments(&$output)
{
$lines = explode("\n", $output);
$output = "";
// try to keep mem. use down
$linecount = count($lines);
$in_comment = false;
for($i = 0; $i < $linecount; $i++)
{
if( preg_match("/^\/\*/", preg_quote($lines[$i])) )
{
$in_comment = true;
}
if( !$in_comment )
{
$output .= $lines[$i] . "\n";
}
if( preg_match("/\*\/$/", preg_quote($lines[$i])) )
{
$in_comment = false;
}
}
unset($lines);
return $output;
}
//
// remove_remarks will strip the sql comment lines out of an uploaded sql file
//
function remove_remarks($sql)
{
$lines = explode("\n", $sql);
// try to keep mem. use down
$sql = "";
$linecount = count($lines);
$output = "";
for ($i = 0; $i < $linecount; $i++)
{
if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0))
{
if (isset($lines[$i][0]) && $lines[$i][0] != "#")
{
$output .= $lines[$i] . "\n";
}
else
{
$output .= "\n";
}
// Trading a bit of speed for lower mem. use here.
$lines[$i] = "";
}
}
return $output;
}
//
// split_sql_file will split an uploaded sql file into single sql statements.
// Note: expects trim() to have already been run on $sql.
//
function split_sql_file($sql, $delimiter)
{
// Split up our string into "possible" SQL statements.
$tokens = explode($delimiter, $sql);
// try to save mem.
$sql = "";
$output = array();
// we don't actually care about the matches preg gives us.
$matches = array();
// this is faster than calling count($oktens) every time thru the loop.
$token_count = count($tokens);
for ($i = 0; $i < $token_count; $i++)
{
// Don't wanna add an empty string as the last thing in the array.
if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0)))
{
// This is the total number of single quotes in the token.
$total_quotes = preg_match_all("/'/", $tokens[$i], $matches);
// Counts single quotes that are preceded by an odd number of backslashes,
// which means they're escaped quotes.
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches);
$unescaped_quotes = $total_quotes - $escaped_quotes;
// If the number of unescaped quotes is even, then the delimiter did NOT occur inside a string literal.
if (($unescaped_quotes % 2) == 0)
{
// It's a complete sql statement.
$output[] = $tokens[$i];
// save memory.
$tokens[$i] = "";
}
else
{
// incomplete sql statement. keep adding tokens until we have a complete one.
// $temp will hold what we have so far.
$temp = $tokens[$i] . $delimiter;
// save memory..
$tokens[$i] = "";
// Do we have a complete statement yet?
$complete_stmt = false;
for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++)
{
// This is the total number of single quotes in the token.
$total_quotes = preg_match_all("/'/", $tokens[$j], $matches);
// Counts single quotes that are preceded by an odd number of backslashes,
// which means they're escaped quotes.
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches);
$unescaped_quotes = $total_quotes - $escaped_quotes;
if (($unescaped_quotes % 2) == 1)
{
// odd number of unescaped quotes. In combination with the previous incomplete
// statement(s), we now have a complete statement. (2 odds always make an even)
$output[] = $temp . $tokens[$j];
// save memory.
$tokens[$j] = "";
$temp = "";
// exit the loop.
$complete_stmt = true;
// make sure the outer loop continues at the right point.
$i = $j;
}
else
{
// even number of unescaped quotes. We still don't have a complete statement.
// (1 odd and 1 even always make an odd)
$temp .= $tokens[$j] . $delimiter;
// save memory.
$tokens[$j] = "";
}
} // for..
} // else
}
}
return $output;
}
$dbms_schema = 'yourfile.sql';
$sql_query = #fread(#fopen($dbms_schema, 'r'), #filesize($dbms_schema)) or die('problem ');
$sql_query = remove_remarks($sql_query);
$sql_query = split_sql_file($sql_query, ';');
$host = 'localhost';
$user = 'user';
$pass = 'pass';
$db = 'database_name';
// mysql_* is deprecated, prefer using mysqli_* instead
// mysql_connect($host,$user,$pass) or die('error connection');
// mysql_select_db($db) or die('error database selection');
$connection = mysqli_connect($host,$user,$pass) or die('error connection');
mysqli_select_db($connection, $db) or die('error database selection');
$i=1;
foreach($sql_query as $sql){
echo $i++;
echo "<br />";
// mysql_* is deprecated, prefer using mysqli_* instead
// mysql_query($sql) or die('error in query');
mysqli_query($connection, $sql) or die('error in query');
}
I'm getting the feeling that everyone here who's answered this question doesn't know what it's like to be a web application developer who allows people to install the application on their own servers. Shared hosting, especially, doesn't allow you to use SQL like the "LOAD DATA" query mentioned previously. Most shared hosts also don't allow you to use shell_exec.
Now, to answer the OP, your best bet is to just build out a PHP file that contains your queries in a variable and can just run them. If you're determined to parse .sql files, you should look into phpMyAdmin and get some ideas for getting data out of .sql files that way. Look around at other web applications that have installers and you'll see that, rather than use .sql files for their queries, they just package them up in PHP files and just run each string through mysql_query or whatever it is that they need to do.
The simplest solution is to use shell_exec() to run the mysql client with the SQL script as input. This might run a little slower because it has to fork, but you can write the code in a couple of minutes and then get back to working on something useful. Writing a PHP script to run any SQL script could take you weeks.
Supporting SQL scripts is more complex than what people are describing here, unless you're certain that your script contains only a subset of the functionality of scripts. Below are some examples of things that may appear in an ordinary SQL script that make it complex to code a script to interpret it line by line.
-- Comment lines cannot be prepared as statements
-- This is a MySQL client tool builtin command.
-- It cannot be prepared or executed by server.
USE testdb;
-- This is a multi-line statement.
CREATE TABLE foo (
string VARCHAR(100)
);
-- This statement is not supported as a prepared statement.
LOAD DATA INFILE 'datafile.txt' INTO TABLE foo;
-- This statement is not terminated with a semicolon.
DELIMITER //
-- This multi-line statement contains a semicolon
-- but not as the statement terminator.
CREATE PROCEDURE simpleproc (OUT param1 INT)
BEGIN
SELECT COUNT(*) INTO param1 FROM foo;
END
//
If you only support a subset of SQL scripts, excluding some corner cases such as those above, it's relatively easy to write a PHP script that reads a file and executes the SQL statements within the file. But if you want to support any valid SQL script, that's much more complex.
See also my answers to these related questions:
Running MySQL *.sql files in PHP
is it possible to call a sql script from a stored procedure in another sql script?
PHP: multiple SQL queries in one mysql_query statement
In my projects I've used next solution:
<?php
/**
* Import SQL from file
*
* #param string path to sql file
*/
function sqlImport($file)
{
$delimiter = ';';
$file = fopen($file, 'r');
$isFirstRow = true;
$isMultiLineComment = false;
$sql = '';
while (!feof($file)) {
$row = fgets($file);
// remove BOM for utf-8 encoded file
if ($isFirstRow) {
$row = preg_replace('/^\x{EF}\x{BB}\x{BF}/', '', $row);
$isFirstRow = false;
}
// 1. ignore empty string and comment row
if (trim($row) == '' || preg_match('/^\s*(#|--\s)/sUi', $row)) {
continue;
}
// 2. clear comments
$row = trim(clearSQL($row, $isMultiLineComment));
// 3. parse delimiter row
if (preg_match('/^DELIMITER\s+[^ ]+/sUi', $row)) {
$delimiter = preg_replace('/^DELIMITER\s+([^ ]+)$/sUi', '$1', $row);
continue;
}
// 4. separate sql queries by delimiter
$offset = 0;
while (strpos($row, $delimiter, $offset) !== false) {
$delimiterOffset = strpos($row, $delimiter, $offset);
if (isQuoted($delimiterOffset, $row)) {
$offset = $delimiterOffset + strlen($delimiter);
} else {
$sql = trim($sql . ' ' . trim(substr($row, 0, $delimiterOffset)));
query($sql);
$row = substr($row, $delimiterOffset + strlen($delimiter));
$offset = 0;
$sql = '';
}
}
$sql = trim($sql . ' ' . $row);
}
if (strlen($sql) > 0) {
query($row);
}
fclose($file);
}
/**
* Remove comments from sql
*
* #param string sql
* #param boolean is multicomment line
* #return string
*/
function clearSQL($sql, &$isMultiComment)
{
if ($isMultiComment) {
if (preg_match('#\*/#sUi', $sql)) {
$sql = preg_replace('#^.*\*/\s*#sUi', '', $sql);
$isMultiComment = false;
} else {
$sql = '';
}
if(trim($sql) == ''){
return $sql;
}
}
$offset = 0;
while (preg_match('{--\s|#|/\*[^!]}sUi', $sql, $matched, PREG_OFFSET_CAPTURE, $offset)) {
list($comment, $foundOn) = $matched[0];
if (isQuoted($foundOn, $sql)) {
$offset = $foundOn + strlen($comment);
} else {
if (substr($comment, 0, 2) == '/*') {
$closedOn = strpos($sql, '*/', $foundOn);
if ($closedOn !== false) {
$sql = substr($sql, 0, $foundOn) . substr($sql, $closedOn + 2);
} else {
$sql = substr($sql, 0, $foundOn);
$isMultiComment = true;
}
} else {
$sql = substr($sql, 0, $foundOn);
break;
}
}
}
return $sql;
}
/**
* Check if "offset" position is quoted
*
* #param int $offset
* #param string $text
* #return boolean
*/
function isQuoted($offset, $text)
{
if ($offset > strlen($text))
$offset = strlen($text);
$isQuoted = false;
for ($i = 0; $i < $offset; $i++) {
if ($text[$i] == "'")
$isQuoted = !$isQuoted;
if ($text[$i] == "\\" && $isQuoted)
$i++;
}
return $isQuoted;
}
function query($sql)
{
global $mysqli;
//echo '#<strong>SQL CODE TO RUN:</strong><br>' . htmlspecialchars($sql) . ';<br><br>';
if (!$query = $mysqli->query($sql)) {
throw new Exception("Cannot execute request to the database {$sql}: " . $mysqli->error);
}
}
set_time_limit(0);
$mysqli = new mysqli('localhost', 'root', '', 'test');
$mysqli->set_charset("utf8");
header('Content-Type: text/html;charset=utf-8');
sqlImport('import.sql');
echo "Peak MB: ", memory_get_peak_usage(true)/1024/1024;
On test sql file (41Mb) memory peak usage: 3.25Mb
mysqli can run multiple queries separated by a ;
you could read in the whole file and run it all at once using mysqli_multi_query()
But, I'll be the first to say that this isn't the most elegant solution.
Since I can't comment on answer, beware to use following solution:
$db = new PDO($dsn, $user, $password);
$sql = file_get_contents('file.sql');
$qr = $db->exec($sql);
There is a bug in PHP PDO https://bugs.php.net/bug.php?id=61613
db->exec('SELECT 1; invalidstatement; SELECT 2');
won't error out or return false (tested on PHP 5.5.14).
My suggestion would be to look at the sourcecode of PHPMyBackup. It's an automated PHP SQL loader. You will find that mysql_query only loads one query at a time, and projects like PHPMyAdmin and PHPMyBackup have already done the hard work for you of parsing the SQL the correct way. Please don't re-invent that wheel :P
An updated solution of Plahcinski solution. Alternatively you can use fopen and fread for bigger files:
$fp = file('database.sql', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$query = '';
foreach ($fp as $line) {
if ($line != '' && strpos($line, '--') === false) {
$query .= $line;
if (substr($query, -1) == ';') {
mysql_query($query);
$query = '';
}
}
}
mysql_query("LOAD DATA LOCAL INFILE '/path/to/file' INTO TABLE mytable");
Briefly, the way I have done this is:
Read the file (a db dump eg $ mysqldump db > db.sql)
$sql = file_get_contents(db.sql);
Import it using mysqli::multi_query
if ($mysqli->multi_query($sql)) {
$mysqli->close();
} else {
throw new Exception ($mysqli->error);
}
Watch out mysqli_query supports async queries. More here: http://php.net/manual/en/mysqli.multi-query.php and here https://stackoverflow.com/a/6652908/2002493
I noticed that the PostgreSQL PDO driver does not allow you to run scripts separated by semicolons. In order to run a .sql file on any database using PDO it is necessary to split the statements in PHP code yourself. Here is a solution that seems to work quite well:
https://github.com/diontruter/migrate/blob/master/src/Diontruter/Migrate/SqlScriptParser.php
The referenced class has done the trick for me in a database independent way, please message me if there are any issues. Here is how you could use the script after adding it to your project:
$pdo = new PDO($connectionString, $userName, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$parser = new SqlScriptParser();
$sqlStatements = $parser->parse($fileName);
foreach ($sqlStatements as $statement) {
$distilled = $parser->removeComments($statement);
if (!empty($distilled)) {
$statement = $pdo->prepare($sql);
$affectedRows = $statement->execute();
}
}
Are you sure that its not one query per line? Your text editor may be wrapping lines, but in reality each query may be on a single line.
At any rate, olle's method seems best. If you have reasons to run queries one at time, you should be able to read in your file line by line, then use the semicolon at the end of each query to delimit. You're much better off reading in a file line by line than trying to split an enormous string, as it will be much kinder to your server's memory. Example:
$query = '';
$handle = #fopen("/sqlfile.sql", "r");
if ($handle) {
while (!feof($handle)) {
$query.= fgets($handle, 4096);
if (substr(rtrim($query), -1) === ';') {
// ...run your query, then unset the string
$query = '';
}
}
fclose($handle);
}
Obviously, you'll need to consider transactions and the rest if you're running a whole lot of queries in a batch, but it's probably not a big deal for a new-install script.
Unless you plan to import huge .sql files, just read the entire file into memory, and run it as a query.
It's been a while since I've used PHP, so, pseudo code:
all_query = read_file("/my/file.sql")
con = mysql_connect("localhost")
con.mysql_select_db("mydb")
con.mysql_query(all_query)
con.close()
Unless the files are huge (say, over several megabytes), there's no reason to execute it line-at-a-time, or try and split it into multiple queries (by splitting using ;, which as I commented on cam8001's answer, will break if the query has semi-colons within strings)..
Works on Navicat dumps. Might need to dump the first /* */ comment navicat puts in.
$file_content = file('myfile.sql');
$query = "";
foreach($file_content as $sql_line){
if(trim($sql_line) != "" && strpos($sql_line, "--") === false){
$query .= $sql_line;
if (substr(rtrim($query), -1) == ';'){
echo $query;
$result = mysql_query($query)or die(mysql_error());
$query = "";
}
}
}
This The Best Code For restore sql by php can use 100% Goooood!
Thank A lot
$file_content = file('myfile.sql');
$query = "";
foreach($file_content as $sql_line){
if(trim($sql_line) != "" && strpos($sql_line, "--") === false){
$query .= $sql_line;
if (substr(rtrim($query), -1) == ';'){
echo $query;
$result = mysql_query($query)or die(mysql_error());
$query = "";
}
}
}
Try This:
// SQL File
$SQLFile = 'YourSQLFile.sql';
// Server Name
$hostname = 'localhost';
// User Name
$db_user = 'root';
// User Password
$db_password = '';
// DBName
$database_name = 'YourDBName';
// Connect MySQL
$link = mysql_connect($hostname, $db_user, $db_password);
if (!$link) {
die("MySQL Connection error");
}
// Select MySQL DB
mysql_select_db($database_name, $link) or die("Wrong MySQL Database");
// Function For Run Multiple Query From .SQL File
function MultiQuery($sqlfile, $sqldelimiter = ';') {
set_time_limit(0);
if (is_file($sqlfile) === true) {
$sqlfile = fopen($sqlfile, 'r');
if (is_resource($sqlfile) === true) {
$query = array();
echo "<table cellspacing='3' cellpadding='3' border='0'>";
while (feof($sqlfile) === false) {
$query[] = fgets($sqlfile);
if (preg_match('~' . preg_quote($sqldelimiter, '~') . '\s*$~iS', end($query)) === 1) {
$query = trim(implode('', $query));
if (mysql_query($query) === false) {
echo '<tr><td>ERROR:</td><td> ' . $query . '</td></tr>';
} else {
echo '<tr><td>SUCCESS:</td><td>' . $query . '</td></tr>';
}
while (ob_get_level() > 0) {
ob_end_flush();
}
flush();
}
if (is_string($query) === true) {
$query = array();
}
}
echo "</table>";
return fclose($sqlfile);
}
}
return false;
}
/* * * Use Function Like This: ** */
MultiQuery($SQLFile);
The easiest and fastest way to load & parse phpmyadmin dump or mysql dump file..
$ mysql -u username -p -h localhost dbname < dumpfile.sql
None of the solutions I have seen here deal with needing to change the delimiter while creating a stored procedure on a server where I can't count on having access to LOAD DATA INFILE. I was hoping to find that someone had already solved this without having to scour the phpMyAdmin code to figure it out. Like others, I too was in the process of looking for someone else's GPL'ed way of doing it since I am writing GPL code myself.
Some PHP libraries can parse a SQL file made of multiple SQL statements, explode it properly (not using a simple ";" explode, naturally), and the execute them.
For instance, check Phing's PDOSQLExecTask
Just to restate the problem for everyone:
PHP's mysql_query, automatically end-delimits each SQL commands, and additionally is very vague about doing so in its manual. Everything beyond one command will yield an error.
On the other mysql_query is fine with a string containing SQL-style comments, \n, \r..
The limitation of mysql_query reveals itself in that the SQL parser reports the problem to be directly at the next command e.g.
You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'INSERT INTO `outputdb:`
(`intid`, `entry_id`, `definition`) VALUES...
Here is a quick solution:
(assuming well formatted SQL;
$sqlCmds = preg_split("/[\n|\t]*;[\n|\t]*[\n|\r]$/", $sqlDump);
Many hosts will not allow you to create your own database through PHP, but you seem to have solved that.
Once the DB has been created, you can manipulate and populate it simply:
mysql_connect("localhost");
mysql_query("SOURCE file.sql");
Some guys (Plahcinski) suggested this code:
$file_content = file('myfile.sql');
$query = "";
foreach($file_content as $sql_line){
if(trim($sql_line) != "" && strpos($sql_line, "--") === false){
$query .= $sql_line;
if (substr(rtrim($query), -1) == ';'){
echo $query;
$result = mysql_query($query)or die(mysql_error());
$query = "";
}
}
}
but I would update it with the one which worked for me:
//selecting my database
$database = 'databaseTitleInFile';
$selectDatabase = mysql_select_db($database, $con);
if(! $selectDatabase )
{
die('Could not select the database: ' . mysql_error());
}
echo "The database " . $database . " selected successfully\n";
//reading the file
$file_path='..\yourPath\to\File';
if(!file_exists($file_path)){
echo "File Not Exists";
}
$file_content = file_get_contents($file_path);
$array = explode("\n", $file_content)
//making queries
$query = "";
foreach($array as $sql_line){
$sql_line=trim($sql_line);
if($sql_line != "" && substr($sql_line, 0, 2) === "--" && strpos($sql_line, "/*") === false){
$query .= $sql_line;
if (substr(rtrim($query), -1) == ';'){
$result = mysql_query($query)or die(mysql_error());
$query = "";
}
}
}
because it is more comprehensive. ;-)
This may be helpful -->
More or less what it does is to first take the string given to the function (the file_get_contents() value of your file.sql) and remove all the line breaks. Then it splits the data by the ";" character. Next it goes into a while loop, looking at each line of the array that is created. If the line contains the " ` " character, it will know it is a query and execture the myquery() function for the given line data.
Code:
function myquery($query) {
mysql_connect(dbhost, dbuser, dbpass);
mysql_select_db(dbname);
$result = mysql_query($query);
if (!mysql_errno() && #mysql_num_rows($result) > 0) {
}
else {
$result="not";
}
mysql_close();
return $result;
}
function mybatchquery ($str) {
$sql = str_replace("\n","",$str)
$sql = explode(";",$str);
$x=0;
while (isset($str[$x])) {
if (preg_match("/(\w|\W)+`(\w|\W)+) {
myquery($str[$x]);
}
$x++
}
return TRUE;
}
function myrows($result) {
$rows = #mysql_num_rows($result);
return $rows;
}
function myarray($result) {
$array = mysql_fetch_array($result);
return $array;
}
function myescape($query) {
$escape = mysql_escape_string($query);
return $escape;
}
$str = file_get_contents("foo.sql");
mybatchquery($str);
$sql = file_get_contents("sql.sql");
Seems to be the simplest answer
I use this all the time:
$sql = explode(";",file_get_contents('[your dump file].sql'));//
foreach($sql as $query)
mysql_query($query);
I hope the following code will solve your problem pretty well.
//Empty all tables' contents
$result_t = mysql_query("SHOW TABLES");
while($row = mysql_fetch_assoc($result_t))
{
mysql_query("TRUNCATE " . $row['Tables_in_' . $mysql_database]);
}
// Temporary variable, used to store current query
$templine = '';
// Read in entire file
$lines = file($filename);
// Loop through each line
foreach ($lines as $line)
{
// Skip it if it's a comment
if (substr($line, 0, 2) == '--' || $line == '')
continue;
// Add this line to the current segment
$templine .= $line;
// If it has a semicolon at the end, it's the end of the query
if (substr(trim($line), -1, 1) == ';')
{
// Perform the query
mysql_query($templine) or print('Error performing query \'<strong>' . $templine . '\': ' . mysql_error() . '<br /><br />');
// Reset temp variable to empty
$templine = '';
}
}
?>
this actually worked for me:
/* load sql-commands from a sql file */
function loadSQLFromFile($url)
{
// ini_set ( 'memory_limit', '512M' );
// set_time_limit ( 0 );
global $settings_database_name;
global $mysqli_object; global $worked; $worked = false;
$sql_query = "";
// read line by line
$lines = file($url);
$count = count($lines);
for($i = 0;$i<$count;$i++)
{
$line = $lines[$i];
$cmd3 = substr($line, 0, 3);
$cmd4 = substr($line, 0, 4);
$cmd6 = substr($line, 0, 6);
if($cmd3 == "USE")
{
// cut away USE ``;
$settings_database_name = substr($line, 5, -3);
}
else if($cmd4 == "DROP")
{
$mysqli_object->query($line); // execute this line
}
else if(($cmd6 == "INSERT") || ($cmd6 == "CREATE"))
{
// sum all lines up until ; is detected
$multiline = $line;
while(!strstr($line, ';'))
{
$i++;
$line = $lines[$i];
$multiline .= $line;
}
$multiline = str_replace("\n", "", $multiline); // remove newlines/linebreaks
$mysqli_object->query($multiline); // execute this line
}
}
return $worked;
}
?>
I have an environment where no mysql tool or phpmyadmin just my php application connecting to a mysql server on a different host but I need to run scripts exported by mysqldump or myadmin. To solve the problem I created a script multi_query as I mentioned here
It can process mysqldump output and phpmyadmin exports without mysql command line tool. I also made some logic to process multiple migration files based on timestamp stored in DB like Rails. I know it needs more error handling but currently does the work for me.
Check it out: https://github.com/kepes/php-migration
It's pure php and don't need any other tools. If you don't process user input with it only scripts made by developers or export tools you can use it safely.
This is from a project I am working on. Basically takes any text file and extracts the SQL statements while ignoring comments and gratuitous line breaks.
<?php
/*
ingestSql(string) : string
Read the contents of a SQL batch file, stripping away comments and
joining statements that are broken over multiple lines with the goal
of producing lines of sql statements that can be successfully executed
by PDO exec() or execute() functions.
For example:
-- My SQL Batch
CREATE TABLE foo(
bar VARCHAR(80),
baz INT NOT NULL);
Becomes:
CREATE TABLE foo(bar VARCHAR(80), baz INT NOT NULL);
*/
function ingestSql($sqlFilePath=__DIR__ . "/create-db.sql") {
$sqlFile = file($sqlFilePath);
$ingestedSql = "";
$statement = "";
foreach($sqlFile as $line) {
// Ignore anything between a double-dash and the end of the line.
$commentStart = strpos($line, "--");
if ($commentStart !== false) {
$line = substr($line, 0, $commentStart);
}
// Only process non-blank lines.
if (strlen($line)) {
// Remove any leading and trailing whitespace and append what's
// left of the line to the current statement.
$line = trim($line);
$statement .= $line;
// A semi-colon ends the current statement. Otherwise what was a
// newline becomes a single space;
if (substr($statement, -1) == ";") {
$ingestedSql .= $statement;
$statement = "\n";
}
else {
$statement .= " ";
}
}
}
return $ingestedSql;
}
?>

Categories