summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorMalf Furious <m@lfurio.us>2017-05-13 18:48:09 -0400
committerMalf Furious <m@lfurio.us>2017-05-13 18:48:09 -0400
commited7b14b506094577a4b727f6f962777c245a3c64 (patch)
treea70c3bc525df54327ffec9af148f64d956c3bfce /app
parente2d3d894f024169cd84c93913db88f3864954532 (diff)
parent0cceafe1831487c61eaac1d3a71a8d3039a9a5b8 (diff)
downloadscrott-ed7b14b506094577a4b727f6f962777c245a3c64.tar.gz
scrott-ed7b14b506094577a4b727f6f962777c245a3c64.zip
Merge branch 'feature/heads' into dev
Diffstat (limited to 'app')
-rw-r--r--app/class/globals.php81
-rw-r--r--app/class/image.php70
-rw-r--r--app/class/object.class.php88
-rw-r--r--app/df.php102
-rw-r--r--app/dynmic/bgs/.gitignore3
-rw-r--r--app/dynmic/heads/.gitignore3
6 files changed, 347 insertions, 0 deletions
diff --git a/app/class/globals.php b/app/class/globals.php
index 8bfa029..615efa6 100644
--- a/app/class/globals.php
+++ b/app/class/globals.php
@@ -20,6 +20,19 @@
define("__VERSION__", "v0.0");
/*
+ * These global variables are arrays of strings logged by Scrott business
+ * logic to report errors, warnings, or informational responses to the
+ * user in cases where an exception doesn't need to be thrown.
+ */
+define("ERROR", "errorlist");
+define("WARNING", "warninglist");
+define("NOTICE", "noticelist");
+
+$_SCROTT[ERROR] = array();
+$_SCROTT[WARNING] = array();
+$_SCROTT[NOTICE] = array();
+
+/*
* Get the application root path. This is an absolute path on the server.
*/
function ar() : string
@@ -63,4 +76,72 @@ function require_https() : void
redirect("https://" . $_SERVER['SERVER_NAME'] . ap());
}
+/*
+ * Check for errors, warnings, or notices
+ */
+function isError(string $level) : bool
+{
+ global $_SCROTT;
+ return count($_SCROTT[$level]) > 0;
+}
+
+/*
+ * Log an error, warning, or notice
+ */
+function logError(string $level, string $error) : void
+{
+ global $_SCROTT;
+ $_SCROTT[$level][] = $error;
+}
+
+/*
+ * Get an array of all errors, warnings, or notices
+ */
+function getErrors(string $level) : array
+{
+ global $_SCROTT;
+ return $_SCROTT[$level];
+}
+
+/*
+ * Save an uploaded file and impose some constraints on supplied
+ * data. Caller can optionally pass some strings by reference to
+ * be given the supplied file's original name and mime-type.
+ * Maxsize is in bytes. If this function returns false, the
+ * appropriate error will be logged.
+ */
+function saveFile(array $file, string $path, int $maxsize, ?array $allowedMime = NULL,
+ ?string &$origName = NULL, ?string &$origMime = NULL) : bool
+{
+ if ($file['error'] > 0)
+ {
+ if ($file['error'] != UPLOAD_ERR_NO_FILE)
+ logError(ERROR, "An unknown error occurred");
+
+ return false;
+ }
+
+ if ($file['size'] > $maxsize)
+ {
+ logError(ERROR, "File must be no larger than " . $maxsize . " bytes");
+ return false;
+ }
+
+ if (is_array($allowedMime) && array_search($file['type'], $allowedMime) === false)
+ {
+ logError(ERROR, "File type is not supported");
+ return false;
+ }
+
+ if (!move_uploaded_file($file['tmp_name'], $path))
+ {
+ logError(ERROR, "Error saving uploaded file");
+ return false;
+ }
+
+ $origName = $file['name'];
+ $origMime = $file['type'];
+ return true;
+}
+
?>
diff --git a/app/class/image.php b/app/class/image.php
new file mode 100644
index 0000000..6b73cae
--- /dev/null
+++ b/app/class/image.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * SCROTT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to UNLICENSE
+ */
+
+/*
+ * This file defines custom image manipulation routines for Scrott. All
+ * are available in the global namespace.
+ */
+
+/*
+ * Mappings from image MIME types to PHP open/write functions
+ */
+$_IMG_OPEN_FUNCS['image/jpeg'] = "imagecreatefromjpeg";
+$_IMG_OPEN_FUNCS['image/jpg'] = "imagecreatefromjpeg";
+$_IMG_OPEN_FUNCS['image/png'] = "imagecreatefrompng";
+
+$_IMG_WRITE_FUNCS['image/jpeg'] = "imagejpeg";
+$_IMG_WRITE_FUNCS['image/jpg'] = "imagejpeg";
+$_IMG_WRITE_FUNCS['image/png'] = "imagepng";
+
+/*
+ * Open the given image and crop it, such that the result is a square
+ * image whose side length is equal to the smaller of the original
+ * dimensions and whose area is centered on the area of the original
+ * image. The resulting image is written to the same path as provided,
+ * overwriting the image.
+ */
+function imageSquareCrop(string $uri) : bool
+{
+ global $_IMG_OPEN_FUNCS;
+ global $_IMG_WRITE_FUNCS;
+
+ $mime = mime_content_type($uri);
+ $img = $_IMG_OPEN_FUNCS[$mime]($uri);
+
+ if ($img === false)
+ return false;
+
+ $wid = imagesx($img);
+ $hei = imagesy($img);
+
+ $rec = array();
+ $rec['width'] = min($wid, $hei);
+ $rec['height'] = $rec['width'];
+ $rec['y'] = ($hei / 2) - ($rec['height'] / 2);
+ $rec['x'] = ($wid / 2) - ($rec['width'] / 2);
+
+ $cropped = imagecrop($img, $rec);
+ imagedestroy($img);
+
+ if ($cropped === false)
+ return false;
+
+ $ret = $_IMG_WRITE_FUNCS[$mime]($cropped, $uri);
+ imagedestroy($cropped);
+
+ return $ret;
+}
+
+?>
diff --git a/app/class/object.class.php b/app/class/object.class.php
index 14ab891..7c80b5b 100644
--- a/app/class/object.class.php
+++ b/app/class/object.class.php
@@ -13,6 +13,7 @@
*/
require_once "class/table.class.php";
+require_once "class/image.php";
/*
* This is a generic database object. This is a supertype of all Scrott
@@ -21,6 +22,17 @@ require_once "class/table.class.php";
class object extends table
{
/*
+ * Constants used for uploading images
+ */
+ public const HEAD_MAXSIZE = 1048576; // 1Mb
+ public const BG_MAXSIZE = 1048576; // 1Mb
+ public const IMAGE_MIME = array(
+ "image/jpeg",
+ "image/jpg",
+ "image/png",
+ );
+
+ /*
* Constructor
*/
public function __construct(?string $guid = NULL)
@@ -149,6 +161,82 @@ class object extends table
database::query($query);
return true;
}
+
+ /*
+ * Get the URL to the head image resource for this object
+ */
+ public function getHeadImg() : string
+ {
+ return ar() . "/df.php?d=heads&f=" . $this->guid;
+ }
+
+ /*
+ * Set the head image for this object, overwriting any existing
+ * image. $image should be an uploaded file to PHP, still
+ * unhandled.
+ */
+ public function setHeadImg(array $image) : bool
+ {
+ $path = "dynmic/heads/" . $this->guid;
+
+ if (!saveFile($image, $path, self::HEAD_MAXSIZE, self::IMAGE_MIME))
+ return false;
+
+ if (!imageSquareCrop($path))
+ {
+ $this->rmHeadImg();
+ return false;
+ }
+
+ return true;
+ }
+
+ /*
+ * Remove the head image for this object. This deletes the image
+ * on disk.
+ */
+ public function rmHeadImg() : bool
+ {
+ if (!is_file("dynmic/heads/" . $this->guid))
+ return true;
+
+ return unlink("dynmic/heads/" . $this->guid);
+ }
+
+ /*
+ * Get the URL to the background image resource for this
+ * object. If no image is set, NULL is returned.
+ */
+ public function getBgImg() : ?string
+ {
+ if (!is_file("dynmic/bgs/" . $this->guid))
+ return NULL;
+
+ return ar() . "/df.php?d=bgs&f=" . $this->guid;
+ }
+
+ /*
+ * Set the background image for this object, overwriting any
+ * existing image. $image should be an uploaded file to PHP,
+ * still unhandled.
+ */
+ public function setBgImg(array $image) : bool
+ {
+ $path = "dynmic/bgs/" . $this->guid;
+ return saveFile($image, $path, self::BG_MAXSIZE, self::IMAGE_MIME);
+ }
+
+ /*
+ * Remove the background image for this object. This deletes
+ * the image on disk.
+ */
+ public function rmBgImg() : bool
+ {
+ if (!is_file("dynmic/bgs/" . $this->guid))
+ return true;
+
+ return unlink("dynmic/bgs/" . $this->guid);
+ }
}
?>
diff --git a/app/df.php b/app/df.php
new file mode 100644
index 0000000..a425d57
--- /dev/null
+++ b/app/df.php
@@ -0,0 +1,102 @@
+<?php
+
+/*
+ * SCROTT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to UNLICENSE
+ */
+
+require_once "class/user.class.php";
+
+/*
+ * This file is a proxy script for fetching resources from the /dynmic
+ * directory. This script enforces access-control on HTTP objects such
+ * as images and flat files which are supplied by users.
+ *
+ * Example request:
+ * https://yourdomain.com/scrott/df.php?d=heads&f=a4bf903a
+ *
+ * In cases of error or lack of access privilege, this script will
+ * produce no output and fail silently.
+ */
+
+/*
+ * Serve the resource at the given URI in response to the current
+ * request. When finished, this function will exit PHP and terminate
+ * this script.
+ */
+function serveResource(string $uri) : void
+{
+ $f = fopen($uri, "rb");
+
+ if (!$f)
+ exit;
+
+ header("Content-type: " . mime_content_type($uri));
+ header("Content-length: " . filesize($uri));
+ fpassthru($f);
+ fclose($f);
+
+ exit;
+}
+
+/*
+ * Check the current user's permissions. User must have access
+ * rights for the file's object, unless that object is a user
+ * object and $allowHeadUser is set to true.
+ */
+function checkPermissions(string $guid, bool $allowHeadUser = false) : bool
+{
+ if (!($user = user::getCurrent()))
+ return false;
+
+ $obj = new object($guid);
+
+ if ($allowHeadUser && $obj->objtype == "user")
+ return true;
+
+ return $user->canAccess($obj);
+}
+
+/*
+ * Respond to users' requests for dynamic files
+ */
+function main(string $dir, string $guid) : void
+{
+ try
+ {
+ if (basename($guid) != $guid || $guid == "")
+ return;
+
+ if (!checkPermissions($guid, $dir == "heads"))
+ return;
+
+ switch ($dir)
+ {
+ case "heads":
+ if (file_exists("dynmic/heads/" . $guid))
+ serveResource("dynmic/heads/" . $guid);
+ else
+ serveResource("static/img/null.jpg");
+ break;
+
+ case "bgs":
+ serveResource("dynmic/bgs/" . $guid);
+ break;
+ }
+ }
+ catch (Exception $e)
+ {
+ /* fail silently */
+ }
+}
+
+main($_REQUEST['d'], $_REQUEST['f']);
+
+?>
diff --git a/app/dynmic/bgs/.gitignore b/app/dynmic/bgs/.gitignore
new file mode 100644
index 0000000..e38384e
--- /dev/null
+++ b/app/dynmic/bgs/.gitignore
@@ -0,0 +1,3 @@
+# Track empty directory
+*
+!.gitignore
diff --git a/app/dynmic/heads/.gitignore b/app/dynmic/heads/.gitignore
new file mode 100644
index 0000000..e38384e
--- /dev/null
+++ b/app/dynmic/heads/.gitignore
@@ -0,0 +1,3 @@
+# Track empty directory
+*
+!.gitignore