commit 33bb79a09519fb6783bb0183f952c712f95ea519 Author: nat Date: Wed Feb 21 16:22:19 2024 -0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d11b61d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +testing.sqlite +~* +.*.sw* diff --git a/README.md b/README.md new file mode 100644 index 0000000..25e897f --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# guestbook.php + +This is a drop-in guestbook system using PHP and SQLite3, meant to be simple enough to fit into one file and be configurable by hand with ease. + +## Setup + +Assuming you have PHP installed on your server, setting up this guestbook should be as easy as dropping `guestbook.php` into a folder. It will create the database and necessary tables on the first load. If it doesn't work immediately it's probably a bug and I'd encourage you to reach out to me about it. + +[Have the latest version of PHP installed][1], and make sure you have SQLite3 set up as well. On Debian, if you don't have `php-8.2-sqlite3` installed, you can install it by running: + +``` +$ sudo apt install php8.2-sqlite3 +``` + +I've tested this using PHP 8.2. It seems like the Debian package `php8.3-sqlite3` so some additional setup may be required to get it working with PHP 8.3 if you're on Debian. + +## Configuring + +Several constants are defined using the PHP `define` function, which works as follows: + +```php +define('[name of constant]', [PHP expression]); +``` + +You can change these constants according to your need. You can also search the file for references to them to see where they're used. + +Beneath the PHP script at the top of the file, there's the complete HTML page for the guestbook, including the style sheet in the head tag, that you can adjust to your needs. + +[1]: https://www.php.net/manual/en/install.php + +## "License" + +This software is a gift from me to you. By accepting this gift, we're forming a relationship, and with that comes certain expectations. Namely: + +* When you share this gift with others, you will share it in the same spirit as I share it with you. +* You will not use this gift to hurt people, any living creatures, or the planet diff --git a/guestbook.php b/guestbook.php new file mode 100644 index 0000000..61ea007 --- /dev/null +++ b/guestbook.php @@ -0,0 +1,371 @@ +exec(" + CREATE TABLE IF NOT EXISTS guest ( + hash TEXT, PRIMARY KEY(hash) + ); +"); + +$conn->exec(" + CREATE TABLE IF NOT EXISTS entry ( + guestHash TEXT NOT NULL, + id INTEGER, + name TEXT, + website TEXT, + message TEXT, + published integer default (cast(strftime('%s', 'now') as int)), + + FOREIGN KEY(guestHash) REFERENCES guest(hash), + PRIMARY KEY(id AUTOINCREMENT) + ); +"); + +function stringLengthIsBetween($str, $lower, $upper) { + return strlen($str) >= $lower && strlen($str) <= $upper; +} + +function submissionIsValid($data) { + if (!stringLengthIsBetween($data['name'], 1, 100)) { + return false; + } + + if (!($data['website'] == null || stringLengthIsBetween($data['website'], 3, 100))) { + return false; + } + + if (!stringLengthIsBetween($data['website'], 0, 1000)) { + return false; + } + + return true; +} + +function handleEntrySubmission($db) { + if (!submissionIsValid($_POST)) { + return SubmissionOutcome::Invalid; + } + + + if (preg_match(CHALLENGE_ANSWER_REGEX, $_POST["challengeQuestion"]) === 0) { + return SubmissionOutcome::Shadowban; + } + + if ( + preg_match(SHADOWBAN_REGEX, $_POST["name"]) == 1 || + preg_match(SHADOWBAN_REGEX, $_POST["website"]) == 1 || + preg_match(SHADOWBAN_REGEX, $_POST["message"]) == 1 + ) { + return SubmissionOutcome::Shadowban; + } + + $client_fingerprint = hash('sha256', $_SERVER['REMOTE_ADDR'] . "#" . $_SERVER['REMOTE_ADDR']); + + $latest_entry_stmt = $db->prepare(" + select published from entry + where guestHash = :guestHash + order by published desc + limit 1 + "); + $latest_entry_stmt->bindValue(':guestHash', $client_fingerprint, SQLITE3_TEXT); + $latest_entry_time = $latest_entry_stmt->execute()->fetchArray()['published']; + + if ($_SERVER['REQUEST_TIME'] - $latest_entry_time <= GUESTBOOK_RATE_LIMIT_SECONDS) { + return SubmissionOutcome::RateLimit; + } + + $entry_insert_stmt = $db->prepare(" + insert into entry(guestHash, name, website, message) + values (:guestHash, :name, :website, :message) + "); + $entry_insert_stmt->bindValue(':guestHash', $client_fingerprint, SQLITE3_TEXT); + $entry_insert_stmt->bindValue(':name', $_POST["name"], SQLITE3_TEXT); + $entry_insert_stmt->bindValue(':website', $_POST["website"], SQLITE3_TEXT); + $entry_insert_stmt->bindValue(':message', $_POST["message"], SQLITE3_TEXT); + + + if (!$entry_insert_stmt->execute()) { + return SubmissionOutcome::Failure; + } + + return SubmissionOutcome::Success; +} + +if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $submission_outcome = handleEntrySubmission($conn); +} +?> + + + + + + <?php echo GUESTBOOK_NAME ?> + + + + + +
+

Sign the guestbook

+ +
+ +

Your entry doesn't seem right

+

+ Double check that you inputted the right values, + and are meeting the form's requirements. +

+ +

Something went wrong...

+

+ An error occured on the server. Reach out the the + administrator to see this fixed! +

+ +

Too many submissions

+

+ To prevent spam, we only allow people to submit one entry every + + minutes. Try again later. +

+ +

Success!

+

+ Your entry has been saved to the guestbook. +

+ +
+ + +
+ +
+ + +

+ +
+
+
+
+ +
+ +
    + + query('select count(*) as entryCount from entry'); + $entry_count = $entry_count_result->fetchArray()['entryCount']; + + $entry_list_stmt = $conn->prepare(" + select id, name, website, message from entry + order by id desc + limit :limit offset :offset + "); + $entry_list_stmt->bindValue('limit', GUESTBOOK_PAGE_SIZE, SQLITE3_INTEGER); + $entry_list_stmt->bindValue('offset', GUESTBOOK_PAGE_SIZE * $current_page, SQLITE3_INTEGER); + + $entry_list_results = $entry_list_stmt->execute(); + + $next_entry = $entry_list_results->fetchArray(); + + while ($next_entry) { + ?> +
  1. +

    + +

    +

    + +

    +

    +
    + + Published +

    +
  2. + fetchArray(); + } + + ?> + +
+ + + + +
+ +