<?php

/**
 * mysql_printf formats a query with arguments, escaping strings
 * properly to prevent SQL injections.
 *
 * The format specifiers are %s for a string, %f for a double, %d for an
 * integer and %% for a percent character. No characters other than
 * [%dfs] may appear after a percent character.
 *
 * Examples:
 *
 *	mysql_query(mysql_printf("SELECT * from users where name=%s and pass=%s", $user, $pass));
 *
 *	mysql_query(mysql_printf("SELECT * from ids where id=%d", $id));
 */
function mysql_printf()
{
	$args = func_get_args();

	$allowed_types = array(
		"d" => "integer",
		"f" => "double",
		"s" => "string"
	);
	$fmt = str_split($args[0]);
	$argi = 1;
	$percent = false;
	$result = "";
	foreach ($fmt as $c) {
		if ($percent) {

			/* First, validate the input. */
			switch ($c) {
			case "%":
				break;
			case "d":
			case "f":
			case "s":
				if ($argi >= count($args))
					throw new Exception("Too few arguments ({$argi}) for format string {$fmt}");
				$arg = $args[$argi];
				$argtype = gettype($arg);
				if ($argtype != $allowed_types[$c])
					throw new Exception("Expected {$allowed_types[$c]} for argument {$argi}, but got {$argtype}");
				$argi++;
				break;
			default:
				throw new Exception("Invalid format string: {$fmt}");
			}

			/* Then, process it. */
			switch ($c) {
			case "%":
				$result .= "%";
			case "d":
			case "f":
				$result .= $arg;
				break;
			case "s":
				$result .= "'".mysql_real_escape_string($arg)."'";
				break;
			default:
				assert(false);
			}
			$percent = false;
		} elseif ($c == "%") {
			$percent = true;
		} else {
			$result .= $c;
		}
	}
	if ($percent)
		throw new Exception("Invalid format string: {$fmt}");
	return $result;
}

function test_mysql_printf() {
	assert("username='foo'" == mysql_printf("username=%s", "foo"));
	assert("username='foo\\'bar'" == mysql_printf("username=%s", "foo'bar"));
	assert("'\\\\''\\'''\\\"'" == mysql_printf("%s%s%s", "\\", "'", "\""));
	assert("uid=12345" == mysql_printf("uid=%d", 12345));
	assert("12345" == mysql_printf("%d%d%d%d%d", 1, 2, 3, 4, 5));
	assert("'a''b''c'" == mysql_printf("%s%s%s", "a", "b", "c"));
	assert("balance=0.5" == mysql_printf("balance=%f", 1.0/2.0));
	try {
		mysql_printf("uid=%d", "hello");
		assert(false);
	} catch (Exception $e) {
		// ok
	}
	printf("+OK\n");
}
test_mysql_printf();

?>