From ce06aecf84a8c4724096352c533e29bd15c76710 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 11 Dec 2024 21:05:50 -0600 Subject: [PATCH] Rewrite lib and install to use new database and have better syntax --- .gitignore | 2 + config.php | 11 - cookies.php | 32 - database.php | 71 ++ lib.php | 136 ++-- public/install.php | 1551 ++++++++++++++++++++++---------------------- 6 files changed, 919 insertions(+), 884 deletions(-) create mode 100644 .gitignore delete mode 100644 config.php delete mode 100644 cookies.php create mode 100644 database.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28d54e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.installed +database.db diff --git a/config.php b/config.php deleted file mode 100644 index b1d2606..0000000 --- a/config.php +++ /dev/null @@ -1,11 +0,0 @@ - "localhost", // MySQL server name. (Default: localhost) - "user" => "", // MySQL username. - "pass" => "", // MySQL password. - "name" => "", // MySQL database name. - "prefix" => "dk", // Prefix for table names. (Default: dk) - "secretword" => ""); // Secret word used when hashing information for cookies. - -?> \ No newline at end of file diff --git a/cookies.php b/cookies.php deleted file mode 100644 index 1ffc5df..0000000 --- a/cookies.php +++ /dev/null @@ -1,32 +0,0 @@ - \ No newline at end of file diff --git a/database.php b/database.php new file mode 100644 index 0000000..1f988fc --- /dev/null +++ b/database.php @@ -0,0 +1,71 @@ +prepare($query); + + foreach ($params ?? [] as $k => $v) $stmt->bindValue($p ? $k + 1 : $k, $v, $this->getSQLiteType($v)); + + $start = microtime(true); + $r = $stmt->execute(); + $this->log($query, microtime(true) - $start); + + return $r; + } + + public function exec(string $query): bool + { + $start = microtime(true); + $r = parent::exec($query); + $this->log($query, microtime(true) - $start); + return $r; + } + + public function exists(string $table, string $column, mixed $value, bool $case_insensitive = true): bool + { + if ($case_insensitive) { + $query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1"; + } else { + $query = "SELECT 1 FROM $table WHERE $column = :v LIMIT 1"; + } + + $result = $this->query($query, [':v' => $value]); + return $result->fetchArray(SQLITE3_NUM) !== false; + } + + private function log(string $query, float $time_taken): void + { + $this->count++; + $this->query_time += $time_taken; + $this->log[] = [$query, $time_taken]; + } + + private function getSQLiteType(mixed $value): int + { + return match (true) { + is_int($value) => SQLITE3_INTEGER, + is_float($value) => SQLITE3_FLOAT, + is_null($value) => SQLITE3_NULL, + default => SQLITE3_TEXT + }; + } +} diff --git a/lib.php b/lib.php index 4e7b47e..d8bb741 100644 --- a/lib.php +++ b/lib.php @@ -1,28 +1,18 @@ -query('SELECT * FROM control WHERE id=1 LIMIT 1;'); + $controlrow = $query->fetchArray(SQLITE3_ASSOC); } - $template = gettemplate("admin"); - - // Make page tags for XHTML validation. - $xml = "\n" - . "\n" - . "\n"; - - $finalarray = array( - "title"=>$title, + $page = parsetemplate(gettemplate("admin"), [ + "title"=>$title, "content"=>$content, "totaltime"=>round(getmicrotime() - $starttime, 4), "numqueries"=>$numqueries, "version"=>$version, - "build"=>$build); - $page = parsetemplate($template, $finalarray); - $page = $xml . $page; + "build"=>$build + ]); - if ($controlrow["compression"] == 1) { ob_start("ob_gzhandler"); } - echo $page; - die(); + echo "\n" . $page; + exit; } function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true, $badstart=false) { // Finalize page and output to browser. global $numqueries, $userrow, $controlrow, $version, $build; if (!isset($controlrow)) { - $controlquery = doquery("SELECT * FROM {{table}} WHERE id='1' LIMIT 1", "control"); - $controlrow = mysql_fetch_array($controlquery); + $query = db()->query('SELECT * FROM control WHERE id=1 LIMIT 1;'); + $controlrow = $query->fetchArray(SQLITE3_ASSOC); } if ($badstart == false) { global $starttime; } else { $starttime = $badstart; } - // Make page tags for XHTML validation. - $xml = "\n" - . "\n" - . "\n"; - - $template = gettemplate("primary"); - if ($rightnav == true) { $rightnav = gettemplate("rightnav"); } else { $rightnav = ""; } if ($leftnav == true) { $leftnav = gettemplate("leftnav"); } else { $leftnav = ""; } if ($topnav == true) { @@ -137,14 +111,14 @@ function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true, if (isset($userrow)) { // Get userrow again, in case something has been updated. - $userquery = doquery("SELECT * FROM {{table}} WHERE id='".$userrow["id"]."' LIMIT 1", "users"); + $userquery = db()->query('SELECT * FROM users WHERE id = ? LIMIT 1;', [$userrow['id']]); unset($userrow); - $userrow = mysql_fetch_array($userquery); + $userrow = $userquery->fetchArray(SQLITE3_ASSOC); // Current town name. if ($userrow["currentaction"] == "In Town") { - $townquery = doquery("SELECT * FROM {{table}} WHERE latitude='".$userrow["latitude"]."' AND longitude='".$userrow["longitude"]."' LIMIT 1", "towns"); - $townrow = mysql_fetch_array($townquery); + $townquery = db()->query('SELECT * FROM towns WHERE latitude = ? AND longitude = ? LIMIT 1;', [$userrow["latitude"], $userrow["longitude"]]); + $townrow = $townquery->fetchArray(SQLITE3_ASSOC); $userrow["currenttown"] = "Welcome to ".$townrow["name"].".

"; } else { $userrow["currenttown"] = ""; @@ -188,10 +162,10 @@ function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true, if ($userrow["currenthp"] <= ($userrow["maxhp"]/5)) { $userrow["currenthp"] = "*".$userrow["currenthp"]."*"; } if ($userrow["currentmp"] <= ($userrow["maxmp"]/5)) { $userrow["currentmp"] = "*".$userrow["currentmp"]."*"; } - $spellquery = doquery("SELECT id,name,type FROM {{table}}","spells"); + $spellquery = db()->query('SELECT id, name, type FROM spells;'); $userspells = explode(",",$userrow["spells"]); $userrow["magiclist"] = ""; - while ($spellrow = mysql_fetch_array($spellquery)) { + foreach ($spellquery->fetchArray(SQLITE3_ASSOC) as $spellrow) { $spell = false; foreach($userspells as $a => $b) { if ($b == $spellrow["id"] && $spellrow["type"] == 1) { $spell = true; } @@ -204,9 +178,9 @@ function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true, // Travel To list. $townslist = explode(",",$userrow["towns"]); - $townquery2 = doquery("SELECT * FROM {{table}} ORDER BY id", "towns"); + $townquery2 = db()->query('SELECT * FROM towns ORDER BY id;'); $userrow["townslist"] = ""; - while ($townrow2 = mysql_fetch_array($townquery2)) { + foreach ($townquery2->fetchArray(SQLITE3_ASSOC) as $townrow2) { $town = false; foreach($townslist as $a => $b) { if ($b == $townrow2["id"]) { $town = true; } @@ -215,29 +189,59 @@ function display($content, $title, $topnav=true, $leftnav=true, $rightnav=true, $userrow["townslist"] .= "".$townrow2["name"]."
\n"; } } - } else { - $userrow = array(); + $userrow = []; } - $finalarray = array( - "dkgamename"=>$controlrow["gamename"], + $page = parsetemplate(gettemplate("primary"), [ + "dkgamename"=>$controlrow["gamename"], "title"=>$title, "content"=>$content, "rightnav"=>parsetemplate($rightnav,$userrow), "leftnav"=>parsetemplate($leftnav,$userrow), "topnav"=>$topnav, - "totaltime"=>round(getmicrotime() - $starttime, 4), + "totaltime"=>round(microtime(true) - $starttime, 4), "numqueries"=>$numqueries, "version"=>$version, - "build"=>$build); - $page = parsetemplate($template, $finalarray); - $page = $xml . $page; - - if ($controlrow["compression"] == 1) { ob_start("ob_gzhandler"); } - echo $page; - die(); + "build"=>$build + ]); + echo "\n" . $page; + exit; } -?> +function checkcookies() +{ + $row = false; + + if (isset($_COOKIE["dkgame"])) { + // COOKIE FORMAT: + // {ID} {USERNAME} {PASSWORDHASH} {REMEMBERME} + $theuser = explode(" ",$_COOKIE["dkgame"]); + $query = db()->query('SELECT * FROM users WHERE id = ?, username = ?, password = ? LIMIT 1;', [$theuser[0], $theuser[1], $theuser[2]]); + if ($query === false) { + set_cookie('dkgame', '', -3600); + die("Invalid cookie data. Please log in again."); + } + $row = $query->fetchArray(SQLITE3_ASSOC); + set_cookie('dkgame', implode(" ", $theuser), (int) $theuser[3] === 1 ? time() + 31536000 : 0); + db()->exec('UPDATE users SET onlinetime = CURRENT_TIMESTAMP WHERE id = ? LIMIT 1;', [$theuser[0]]); + } + + return $row; +} + +/** + * Set a cookie with secure and HTTP-only flags. + */ +function set_cookie($name, $value, $expires) +{ + setcookie($name, $value, [ + 'expires' => $expires, + 'path' => '/', + 'domain' => '', // Defaults to the current domain + 'secure' => true, // Ensure the cookie is only sent over HTTPS + 'httponly' => true, // Prevent access to cookie via JavaScript + 'samesite' => 'Strict' // Enforce SameSite=Strict + ]); +} diff --git a/public/install.php b/public/install.php index d956bfc..c07334a 100644 --- a/public/install.php +++ b/public/install.php @@ -1,812 +1,813 @@ - - -Dragon Knight Installation - - -Dragon Knight Installation: Page One

-NOTE: Please ensure you have filled in the correct values in config.php before continuing. Installation will fail if these values are not correct. Also, the MySQL database needs to already exist. This installer script will take care of setting up its structure and content, but the database itself must already exist on your MySQL server before the installer will work.

-Installation for Dragon Knight is a simple two-step process: set up the database tables, then create the admin user. After that, you're done.

-You have two options for database setup: complete or partial. - -Click the appropriate button below for your preferred installation method.

-
-
- OR -
-
- - -END; -echo $page; -die(); - +require_once '../lib.php'; + +define('START', microtime(true)); + +match ((int) $_GET['page'] ?? 0) { + 2 => second(), + 3 => third(), + 4 => fourth(), + 5 => fifth(), + default => first(), +}; + + +/** + * First page - show warnings and gather info + */ +function first() +{ + echo << + + Dragon Knight Installation + + + + Dragon Knight Installation: Page One

+ NOTE: Please ensure you have filled in the correct values in config.php before continuing. Installation will fail if these values are not correct. Also, the MySQL database needs to already exist. This installer script will take care of setting up its structure and content, but the database itself must already exist on your MySQL server before the installer will work.

+ Installation for Dragon Knight is a simple two-step process: set up the database tables, then create the admin user. After that, you're done.

+ You have two options for database setup: complete or partial. + + Click the appropriate button below for your preferred installation method.

+
+
- OR -
+
+ + + END; + + exit; } -function second() { // Second page - set up the database tables. - - global $dbsettings; +/** + * Set up database tables. + */ +function second() +{ echo "Dragon Knight InstallationDragon Knight Installation: Page Two

"; - $prefix = $dbsettings["prefix"]; - $babble = $prefix . "_babble"; - $control = $prefix . "_control"; - $drops = $prefix . "_drops"; - $forum = $prefix . "_forum"; - $items = $prefix . "_items"; - $levels = $prefix . "_levels"; - $monsters = $prefix . "_monsters"; - $news = $prefix . "_news"; - $spells = $prefix . "_spells"; - $towns = $prefix . "_towns"; - $users = $prefix . "_users"; - - if (isset($_POST["complete"])) { $full = true; } else { $full = false; } - -$query = <<"; } else { echo "Error creating Babble Box table."; } -unset($query); -$query = <<"; } else { echo "Error creating Control table."; } -unset($query); + $query = db()->exec(<<"; } else { echo "Error populating Control table."; } -unset($query); + echo $query === true ? 'Babble Box table created.
' : 'Error creating Babble Box table.'; -$query = <<"; } else { echo "Error creating Drops table."; } -unset($query); + $query = db()->exec(<<"; } else { echo "Error populating Drops table."; } -unset($query); -} + echo $query === true ? 'Control table created.
' : 'Error creating Control table.'; -$query = <<"; } else { echo "Error creating Forum table."; } -unset($query); + $query = db()->exec("INSERT INTO control VALUES (1, 'Dragon Knight', 250, 1, '', '', 1, '', 'Mage', 'Warrior', 'Paladin', 'Easy', 1, 'Medium', 1.2, 'Hard', 1.5, 1, 1, 1, 1);"); -$query = <<"; } else { echo "Error creating Items table."; } -unset($query); + echo $query === true ? 'Control table populated.
' : 'Error populating Control table.'; -if ($full == true) { -$query = <<"; } else { echo "Error populating Items table."; } -unset($query); -} + $query = db()->exec(<<"; } else { echo "Error creating Levels table."; } -unset($query); + echo $query == true ? 'Drops table created.
' : 'Error creating Drops table.'; -if ($full == true) { -$query = <<"; } else { echo "Error populating Levels table."; } -unset($query); -} + if ($full) { + $query = db()->exec(<<"; } else { echo "Error creating Monsters table."; } -unset($query); + echo $query === true ? 'Drops table populated.
' : 'Error populating Drops table.'; + } -if ($full == true) { -$query = <<"; } else { echo "Error populating Monsters table."; } -unset($query); -} + $query = db()->exec(<<"; } else { echo "Error creating News table."; } -unset($query); + echo $query === true ? 'Forum table created.
' : 'Error creating Forum table.'; -$query = <<"; } else { echo "Error populating News table."; } -unset($query); + $query = db()->exec(<<"; } else { echo "Error creating Spells table."; } -unset($query); + echo $query === true ? 'Items table created.
' : 'Error creating Items table.'; -if ($full == true) { -$query = <<"; } else { echo "Error populating Spells table."; } -unset($query); -} + if ($full) { + $query = db()->exec(<<"; } else { echo "Error creating Towns table."; } -unset($query); + echo $query === true ? 'Items table populated.
' : 'Error populating Items table.'; + } -if ($full == true) { -$query = <<"; } else { echo "Error populating Towns table."; } -unset($query); -} + $query = db()->exec(<<"; } else { echo "Error creating Users table."; } -unset($query); - - global $start; - $time = round((getmicrotime() - $start), 4); + echo $query === true ? 'Levels table created.
' : 'Error creating Levels table.'; + + if ($full) { + $query = db()->exec(<<' : 'Error populating Levels table.'; + } + + $query = db()->exec(<<' : 'Error creating Monsters table.'; + + if ($full) { + $query = db()->exec(<<' : 'Error populating Monsters table.'; + } + + $query = db()->exec(<<' : 'Error creating News table.'; + + $query = db()->exec("INSERT INTO news VALUES (1, '2004-01-01 12:00:00', 'This is the first news post. Please use the admin control panel to add another one and make this one go away.');"); + + echo $query === true ? 'News table populated.
' : 'Error populating News table.'; + + $query = db()->exec(<<' : 'Error creating Spells table.'; + + if ($full) { + $query = db()->exec(<<' : 'Error populating Spells table.'; + } + + $query = db()->exec(<<' : 'Error creating Towns table.'; + + if ($full) { + $query = db()->exec(<<' : 'Error populating Towns table.'; + } + + $query = db()->exec(<<' : 'Error creating Users table.'; + + $time = round((microtime(true) - START), 4); echo "
Database setup complete in $time seconds.

Click here to continue with installation."; - die(); - + exit; } -function third() { // Third page: gather user info for admin account. - -$page = << - -Dragon Knight Installation - - -Dragon Knight Installation: Page Three

-Now you must create an administrator account so you can use the control panel. Fill out the form below to create your account. You will be able to customize the class names through the control panel once your admin account is created.

-
- - - - - - - - - - -
Username:


Password:
Verify Password:


Email Address:
Verify Email:


Character Name:
Character Class:
Difficulty:
-
- - -END; -echo $page; -die(); +/** + * Gather user info for admin account. + */ +function third() +{ + echo << + + Dragon Knight Installation + + + Dragon Knight Installation: Page Three

+ Now you must create an administrator account so you can use the control panel. Fill out the form below to create your account. You will be able to customize the class names through the control panel once your admin account is created.

+
+ + + + + + + + + +
Username:


Password:
Verify Password:


Email Address:
Verify Email:


Character Class:
Difficulty:
+
+ + + END; + exit; } -function fourth() { // Final page: insert new user row, congratulate the person on a job well done. - - extract($_POST); - if (!isset($username)) { die("Username is required."); } - if (!isset($password1)) { die("Password is required."); } - if (!isset($password2)) { die("Verify Password is required."); } - if ($password1 != $password2) { die("Passwords don't match."); } - if (!isset($email1)) { die("Email is required."); } - if (!isset($email2)) { die("Verify Email is required."); } - if ($email1 != $email2) { die("Emails don't match."); } - if (!isset($charname)) { die("Character Name is required."); } - $password = md5($password1); - - global $dbsettings; - $users = $dbsettings["prefix"] . "_users"; - $query = mysql_query("INSERT INTO $users SET id='1',username='$username',password='$password',email='$email1',verify='1',charname='$charname',charclass='$charclass',regdate=NOW(),onlinetime=NOW(),authlevel='1'") or die(mysql_error()); +/** + * Final page: insert new user row, congratulate the person on a job well done. + */ +function fourth() +{ + $u = trim($_POST['username'] ??= ''); + $e = trim($_POST['email1'] ??= ''); + $ec = trim($_POST['email2'] ??= ''); + $p = $_POST['password1'] ??= ''; + $pc = $_POST['password2'] ??= ''; -$page = << - -Dragon Knight Installation - - -Dragon Knight Installation: Page Four

-Your admin account was created successfully. Installation is complete.

-Be sure to delete install.php from your Dragon Knight directory for security purposes.

-You are now ready to play the game. Note that you must log in through the public section before being allowed into the control panel. Once logged in, an "Admin" link will appear in the Functions box of the left sidebar panel.

-Thank you for using Dragon Knight!

-----

-Optional: Dragon Knight is a free product, and does not require registration of any sort. However, there is an -optional "call home" function in the installer, which notifies the author of your game installation. The ONLY information -transmitted with this function is the URL to your game. This is included mainly to satisfy the author's curiosity about -how many copies of the game are being installed and used. If you choose to submit your URL to the author, please -click here. - - -END; + $errors = []; - echo $page; - die(); + if (empty($u) || strlen($u) < 3 || strlen($u) > 18 || !ctype_alnum(str_replace(' ', '', $u))) { + $errors[] = 'Username is required and must be between 3 and 18 characters long and contain only + alphanumeric characters and spaces.'; + } + if (empty($e) || strlen($e) > 255 || !filter_var($e, FILTER_VALIDATE_EMAIL)) { + $errors[] = 'Email is required must be a valid email address.'; + } + + if ($e !== $ec) { + $errors[] = 'Verify Email must match.'; + } + + if (empty($p) || strlen($p) < 6) { + $errors[] = 'Password is required and must be at least 6 characters long.'; + } + + if ($pc !== $p) { + $errors[] = 'Verify Password must match.'; + } + + if (!empty($errors)) { + echo "
    "; + foreach ($errors as $error) echo "
  • $error
  • "; + echo "
"; + exit; + } + + if (db()->query( + "INSERT INTO users (username, password, email, verify, charclass, authlevel) VALUES (?, ?, ?, 1, ?, 1)", + [$u, password_hash($p, PASSWORD_ARGON2ID), $e, $_POST['charclass']] + ) === false) { + echo "Failed to create user."; + exit; + } + + file_put_contents('../.installed', date('Y-m-d H:i:s')); + + echo << + + Dragon Knight Installation + + + Dragon Knight Installation: Page Four

+ Your admin account was created successfully. Installation is complete.

+ Be sure to delete install.php from your Dragon Knight directory for security purposes.

+ You are now ready to play the game. Note that you must log in through the public section before being allowed into the control panel. Once logged in, an "Admin" link will appear in the Functions box of the left sidebar panel.

+ Thank you for using Dragon Knight!

-----

+ Optional: Dragon Knight is a free product, and does not require registration of any sort. However, there is an + optional "call home" function in the installer, which notifies the author of your game installation. The ONLY information + transmitted with this function is the URL to your game. This is included mainly to satisfy the author's curiosity about + how many copies of the game are being installed and used. If you choose to submit your URL to the author, please + click here. + + + END; + + exit; } -function fifth() { // Call Home function. - - $url = "http://".$_SERVER["SERVER_NAME"].$_SERVER["PHP_SELF"]; - if (mail("jamin@se7enet.com", "Dragon Knight Call Home", "$url") != true) { die("Dragon Knight was unable to send your URL. Please go back and try again, or just continue on to the game."); } - -$page = << - -Dragon Knight Installation - - -Dragon Knight Installation: Page Five

-Thank you for submitting your URL!

-You are now ready to play the game. Note that you must log in through the public section before being allowed into the control panel. Once logged in, an "Admin" link will appear in the Functions box of the left sidebar panel. - - -END; - - echo $page; - die(); - -} +/** + * Call Home function. + */ +function fifth() +{ + if (mail("sky@sharkk.net", "Dragon Knight Call Home", $_SERVER["SERVER_NAME"].$_SERVER["PHP_SELF"]) !== true) { + die('Dragon Knight was unable to send your URL. Please go back and try again, or just continue on to the game.'); + } -?> \ No newline at end of file + echo << + + Dragon Knight Installation + + + Dragon Knight Installation: Page Five

+ Thank you for submitting your URL!

+ You are now ready to play the game. Note that you must log in through the public section before being allowed into the control panel. Once logged in, an "Admin" link will appear in the Functions box of the left sidebar panel. + + + END; + + exit; +}