diff options
Diffstat (limited to 'bootstrap/comments/backend/classes/setup.php')
-rw-r--r-- | bootstrap/comments/backend/classes/setup.php | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/bootstrap/comments/backend/classes/setup.php b/bootstrap/comments/backend/classes/setup.php new file mode 100644 index 0000000..c3576d4 --- /dev/null +++ b/bootstrap/comments/backend/classes/setup.php @@ -0,0 +1,446 @@ +<?php namespace HashOver; + +// Copyright (C) 2010-2018 Jacob Barkdull +// This file is part of HashOver. +// +// HashOver is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// HashOver is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with HashOver. If not, see <http://www.gnu.org/licenses/>. + + +class Setup extends Settings +{ + public $usage; + public $encryption; + public $remoteAccess = false; + public $pageURL; + public $pageTitle; + public $filePath; + public $threadName; + public $commentsDirectory; + public $pagesDirectory; + public $threadDirectory; + public $URLQueryList = array (); + public $URLQueries; + + // Required extensions to check for + public $extensions = array ( + 'date', + 'dom', + 'json', + 'mbstring', + 'openssl', + 'pcre', + 'PDO', + 'SimpleXML' + ); + + // Characters that aren't allowed in directory names + public $reservedCharacters = array ( + '<', + '>', + ':', + '"', + '/', + '\\', + '|', + '?', + '&', + '!', + '*', + '.', + '=', + '_', + '+', + ' ' + ); + + // HashOver-specific URL queries to be ignored + public $ignoredQueries = array ( + 'hashover-reply', + 'hashover-edit' + ); + + // Default metadata + public $metadata = array ( + 'title' => '', + 'url' => '', + 'status' => 'open', + 'latest' => array () + ); + + public function __construct (array $usage) + { + parent::__construct (); + + $this->usage = $usage; + $this->misc = new Misc ($usage['mode']); + + // Check if PHP version is the minimum required + if (version_compare (PHP_VERSION, '5.3.3') < 0) { + $version_parts = explode ('-', PHP_VERSION); + $version = current ($version_parts); + + throw new \Exception ('PHP ' . $version . ' is too old. Must be at least version 5.3.3.'); + } + + // Check for required extensions + $this->extensionsLoaded ($this->extensions); + + // Comments directory path + $this->commentsDirectory = $this->getAbsolutePath ('comments'); + + // Comment threads directory path + $this->pagesDirectory = $this->commentsDirectory . '/threads'; + + // Throw exception if for Blowfish hashing support isn't detected + if ((defined ('CRYPT_BLOWFISH') and CRYPT_BLOWFISH) === false) { + throw new \Exception ('Failed to find CRYPT_BLOWFISH. Blowfish hashing support is required.'); + } + + // Throw exception if notification email is set to the default + if ($this->notificationEmail === 'example@example.com') { + throw new \Exception (sprintf ( + 'You must use a UNIQUE notification e-mail in %s', + $this->getBackendPath ('classes/settings.php') + )); + } + + // Throw exception if encryption key is set to the default + if ($this->encryptionKey === '8CharKey') { + throw new \Exception (sprintf ( + 'You must use a UNIQUE encryption key in %s', + $this->getBackendPath ('classes/settings.php') + )); + } + + // Throw exception if administrative password is set to the default + if ($this->adminPassword === 'password') { + throw new \Exception (sprintf ( + 'You must use a UNIQUE admin password in %s', + $this->getBackendPath ('classes/settings.php') + )); + } + + // Throw exception if the script wasn't requested by this server + if ($this->usage['mode'] !== 'php') { + if ($this->refererCheck () === false) { + throw new \Exception ('External use not allowed.'); + } + } + + // Instantiate encryption class + $this->encryption = new Encryption ($this->encryptionKey); + + // Check if visitor is on mobile device + if (!empty ($_SERVER['HTTP_USER_AGENT'])) { + if (preg_match ('/(android|blackberry|phone|mobile|tablet)/i', $_SERVER['HTTP_USER_AGENT'])) { + // Adjust settings to accommodate + $this->isMobile = true; + $this->imageFormat = 'svg'; + } + } + } + + public function extensionsLoaded (array $extensions) + { + // Throw exceptions if an extension isn't loaded + foreach ($extensions as $extension) { + if (extension_loaded ($extension) === false) { + throw new \Exception ('Failed to detect required extension: ' . $extension . '.'); + } + } + } + + public function getRequest ($key, $default = false) + { + // Return default value if POST and GET are empty + if (empty ($_GET[$key]) and empty ($_POST[$key])) { + return $default; + } + + // Attempt to obtain GET data + if (!empty ($_GET[$key])) { + $request = $_GET[$key]; + } + + // Attempt to obtain POST data + if (!empty ($_POST[$key])) { + $request = $_POST[$key]; + } + + // Strip escape slashes from POST or GET + if (get_magic_quotes_gpc ()) { + $request = stripslashes ($request); + } + + return $request; + } + + protected function getDomainWithPort ($url = '') + { + // Parse URL + $url = parse_url ($url); + + if ($url === false or empty ($url['host'])) { + throw new \Exception ('Failed to obtain domain name.'); + } + + // If URL has a port, return domain with port + if (!empty ($url['port'])) { + return $url['host'] . ':' . $url['port']; + } + + // Otherwise return domain without port + return $url['host']; + } + + protected function setupRemoteAccess () + { + $this->remoteAccess = true; + $this->httpRoot = $this->absolutePath . $this->httpRoot; + $this->syncSettings (); + } + + protected function refererCheck () + { + // No referer set + if (empty ($_SERVER['HTTP_REFERER'])) { + return true; + } + + // Get HTTP referer domain with port + $domain = $this->getDomainWithPort ($_SERVER['HTTP_REFERER']); + + // Check if the script was requested by this server + if ($domain === $this->domain) { + return true; + } + + // Run through allowed domains + foreach ($this->allowedDomains as $allowed_domain) { + $sub_regex = '/^' . preg_quote ('\*\.') . '/S'; + $safe_domain = preg_quote ($allowed_domain); + $domain_regex = preg_replace ($sub_regex, '(?:.*?\.)*', $safe_domain); + $domain_regex = '/^' . $domain_regex . '$/iS'; + + // Check if the script was requested from an allowed domain + if (preg_match ($domain_regex, $domain)) { + // If so, setup remote access + $this->setupRemoteAccess (); + return true; + } + } + + // Check if the usage context is an API + if ($this->usage['context'] === 'api') { + // If so, setup remote access + $this->setupRemoteAccess (); + return true; + } + + return false; + } + + protected function getPageURL () + { + // Attempt to obtain URL via GET or POST + $request = $this->getRequest ('url'); + + // Return on success + if ($request !== false) { + return $request; + } + + // Attempt to obtain URL via HTTP referer + if (!empty ($_SERVER['HTTP_REFERER'])) { + return $_SERVER['HTTP_REFERER']; + } + + // Error on failure + throw new \Exception ('Failed to obtain page URL.'); + } + + protected function sanitizeData ($data = '') + { + // Strip HTML tags from data + $data = strip_tags (html_entity_decode ($data, false, 'UTF-8')); + + // Encode HTML characters in data + $data = htmlspecialchars ($data, false, 'UTF-8', false); + + return $data; + } + + protected function requestData ($data = '', $default = false) + { + // Attempt to obtain data via GET or POST + $request = $this->getRequest ($data, $default); + + // Return on success + if ($request !== $default) { + $request = $this->sanitizeData ($request); + } + + return $request; + } + + public function setThreadName ($name = '') + { + // Requesting the title if told to + if ($name === 'request') { + $name = $this->requestData ('thread', $this->threadName); + } + + // Replace reserved characters with dashes + $name = str_replace ($this->reservedCharacters, '-', $name); + + // Remove multiple dashes + if (mb_strpos ($name, '--') !== false) { + $name = preg_replace ('/-{2,}/', '-', $name); + } + + // Remove leading and trailing dashes + $name = trim ($name, '-'); + + // Final comment directory name + $this->threadDirectory = $this->pagesDirectory . '/' . $name; + $this->threadName = $name; + } + + protected function getIgnoredQueries () + { + // Ignored URL queries list file + $ignored_queries = $this->getAbsolutePath ('config/ignored-queries.json'); + + // Queries to be ignored + $queries = $this->ignoredQueries; + + // Check if ignored URL queries list file exists + if (file_exists ($ignored_queries)) { + // If so, get ignored URL queries list + $data = @file_get_contents ($ignored_queries); + + // Parse ignored URL queries list JSON + $json = @json_decode ($data, true); + + // Check if file parsed successfully + if ($json !== null) { + // If so, merge ignored URL queries file with defaults + $queries = array_merge ($json, $queries); + } + } + + return $queries; + } + + public function setPageURL ($url = '') + { + // Set page URL + $this->pageURL = $url; + + // Request page URL by default + if (empty ($url) or $url === 'request') { + $this->pageURL = $this->getPageURL (); + } + + // Strip HTML tags from page URL + $this->pageURL = strip_tags (html_entity_decode ($this->pageURL, false, 'UTF-8')); + + // Turn page URL into array + $url_parts = parse_url ($this->pageURL); + + // Set initial path + if (empty ($url_parts['path']) or $url_parts['path'] === '/') { + $this->threadName = 'index'; + $this->filePath = '/'; + } else { + // Remove starting slash + $this->threadName = mb_substr ($url_parts['path'], 1); + + // Set file path + $this->filePath = $url_parts['path']; + } + + // Remove unwanted URL queries + if (!empty ($url_parts['query'])) { + $url_queries = explode ('&', $url_parts['query']); + $ignored_queries = $this->getIgnoredQueries (); + + for ($q = 0, $ql = count ($url_queries); $q < $ql; $q++) { + if (!in_array ($url_queries[$q], $ignored_queries, true)) { + $equals = explode ('=', $url_queries[$q]); + + if (!in_array ($equals[0], $ignored_queries, true)) { + $this->URLQueryList[] = $url_queries[$q]; + } + } + } + + $this->URLQueries = implode ('&', $this->URLQueryList); + $this->threadName .= '-' . $this->URLQueries; + } + + // Encode HTML characters in page URL + $this->pageURL = htmlspecialchars ($this->pageURL, false, 'UTF-8', false); + + // Final URL + if (!empty ($url_parts['scheme']) and !empty ($url_parts['host'])) { + $this->pageURL = $url_parts['scheme'] . '://'; + $this->pageURL .= $url_parts['host']; + } else { + throw new \Exception ('URL needs a hostname and scheme.'); + } + + // Add optional port to URL + if (!empty ($url_parts['port'])) { + $this->pageURL .= ':' . $url_parts['port']; + } + + // Add file path + $this->pageURL .= $this->filePath; + + // Add option queries + if (!empty ($this->URLQueries)) { + $this->pageURL .= '?' . $this->URLQueries; + } + + // Set thread directory name to page URL + $this->setThreadName ($this->threadName); + } + + public function setPageTitle ($title = '') + { + // Requesting the title if told to + if ($title === 'request') { + $title = $this->requestData ('title', ''); + } + + // Sanitize page title + $title = $this->sanitizeData ($title); + + // Set page title + $this->pageTitle = $title; + } + + // Weak verification of an admin login + public function adminLogin ($hash) + { + return ($hash === hash ('ripemd160', $this->adminName . $this->adminPassword)); + } + + // Strict verification of an admin login + public function verifyAdmin ($password) + { + return $this->encryption->verifyHash ($this->adminPassword, $password); + } +} |