diff --git a/README.md b/README.md index e5f8ead0..e0f985fe 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Requirements PHP 8.0 is explicitly supported. PHP 7.x should be compatable. 2. MySQL/MariaDB server >= 5.5.3 3. [Composer](https://getcomposer.org/) (To install various packages) -4. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) +4. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) 5. [PHP GD](http://www.php.net/manual/en/intro.image.php) 6. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php) @@ -44,7 +44,7 @@ Installation development version with: git clone git://git.leftypol.org/leftypol/leftypol.git - + 2. run ```composer install``` inside the directory 3. Navigate to ```install.php``` in your web browser and follow the prompts. @@ -80,7 +80,7 @@ find support from a variety of sources: * For support, reply to the sticky on our [/tech/](https://leftypol.org/tech/) board. ### Tinyboard support -vichan, and by extension lainchan and leftypol, is based on a Tinyboard, so both engines have very much in common. These links may be helpful for you as well: +vichan, and by extension lainchan and leftypol, is based on a Tinyboard, so both engines have very much in common. These links may be helpful for you as well: * Tinyboard documentation can be found [here](https://web.archive.org/web/20121016074303/http://tinyboard.org/docs/?p=Main_Page). diff --git a/composer.json b/composer.json index d0345e3b..21fff51a 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "inc/polyfill.php", "inc/error.php", "inc/functions.php", + "inc/functions/hide.php", "inc/functions/net.php" ] }, diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index a884650c..fffd868d 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -18,6 +18,8 @@ RUN apk add --no-cache \ graphicsmagick \ gifsicle \ ffmpeg \ + djvulibre \ + ghostscript \ bind-tools \ gettext \ gettext-dev \ diff --git a/inc/Data/Driver/CacheDriverTrait.php b/inc/Data/Driver/CacheDriverTrait.php new file mode 100644 index 00000000..16e56ce0 --- /dev/null +++ b/inc/Data/Driver/CacheDriverTrait.php @@ -0,0 +1,20 @@ +name = $name; + $this->level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = $this->levelToString($level); + $line = "{$this->name} $lv: $message"; + \error_log($line, 0, null, null); + } + } +} diff --git a/inc/Data/Driver/FileLogDriver.php b/inc/Data/Driver/FileLogDriver.php new file mode 100644 index 00000000..2c9f14a0 --- /dev/null +++ b/inc/Data/Driver/FileLogDriver.php @@ -0,0 +1,61 @@ +fd = \fopen($file_path, 'a'); + if ($this->fd === false) { + throw new \RuntimeException("Unable to open log file at $file_path"); + } + + $this->name = $name; + $this->level = $level; + + // In some cases PHP does not run the destructor. + \register_shutdown_function([$this, 'close']); + } + + public function __destruct() { + $this->close(); + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = $this->levelToString($level); + $line = "{$this->name} $lv: $message\n"; + \flock($this->fd, LOCK_EX); + \fwrite($this->fd, $line); + \fflush($this->fd); + \flock($this->fd, LOCK_UN); + } + } + + public function close() { + \flock($this->fd, LOCK_UN); + \fclose($this->fd); + } +} diff --git a/inc/Data/Driver/LogDriver.php b/inc/Data/Driver/LogDriver.php new file mode 100644 index 00000000..fddc3f27 --- /dev/null +++ b/inc/Data/Driver/LogDriver.php @@ -0,0 +1,22 @@ +inner = new \Memcached(); if (!$this->inner->setOption(\Memcached::OPT_BINARY_PROTOCOL, true)) { - throw new \RuntimeException('Unable to set the memcached protocol!'); + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to set the memcached protocol: '$err'"); } if (!$this->inner->setOption(\Memcached::OPT_PREFIX_KEY, $prefix)) { - throw new \RuntimeException('Unable to set the memcached prefix!'); + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to set the memcached prefix: '$err'"); } - if (!$this->inner->addServers($memcached_server)) { - throw new \RuntimeException('Unable to add the memcached server!'); + + $maybe_unix_path = self::asUnixSocketPath($server_uri); + $is_unix = $maybe_unix_path !== null; + if ($is_unix) { + $server_uri = $maybe_unix_path; + } + + // Memcached keeps the server connections open across requests. + $current_servers = $this->inner->getServerList(); + $found_in_curr = false; + foreach ($current_servers as $curr) { + // Ignore the port if the server is connected with a unix socket. + if ($curr['host'] === $server_uri && ($is_unix || $curr['port'] === $server_port)) { + $found_in_curr = true; + } + } + + if (!$found_in_curr) { + if (!empty($current_servers)) { + if (!$this->inner->resetServerList()) { + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to reset the memcached server list: '$err'"); + } + } + if (!$this->inner->addServer($server_uri, $server_port, $server_weight)) { + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to add memcached servers: '$err'"); + } } } diff --git a/inc/Data/Driver/RedisCacheDriver.php b/inc/Data/Driver/RedisCacheDriver.php index 389fff32..c71c8779 100644 --- a/inc/Data/Driver/RedisCacheDriver.php +++ b/inc/Data/Driver/RedisCacheDriver.php @@ -5,18 +5,17 @@ defined('TINYBOARD') or exit; class RedisCacheDriver implements CacheDriver { + use CacheDriverTrait; + private string $prefix; private \Redis $inner; public function __construct(string $prefix, string $host, ?int $port, ?string $password, int $database) { $this->inner = new \Redis(); - if (str_starts_with($host, 'unix:') || str_starts_with($host, ':')) { - $ret = \explode(':', $host); - if (count($ret) < 2) { - throw new \RuntimeException("Invalid unix socket path $host"); - } - // Unix socket. - $this->inner->connect($ret[1]); + $maybe_unix = self::asUnixSocketPath($host); + + if ($maybe_unix !== null) { + $this->inner->connect($maybe_unix); } elseif ($port === null) { $this->inner->connect($host); } else { diff --git a/inc/Data/Driver/StderrLogDriver.php b/inc/Data/Driver/StderrLogDriver.php new file mode 100644 index 00000000..4c766033 --- /dev/null +++ b/inc/Data/Driver/StderrLogDriver.php @@ -0,0 +1,27 @@ +name = $name; + $this->level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = $this->levelToString($level); + \fwrite(\STDERR, "{$this->name} $lv: $message\n"); + } + } +} diff --git a/inc/Data/Driver/SyslogLogDriver.php b/inc/Data/Driver/SyslogLogDriver.php new file mode 100644 index 00000000..c0df5304 --- /dev/null +++ b/inc/Data/Driver/SyslogLogDriver.php @@ -0,0 +1,35 @@ +level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) { + // CGI + \syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\""); + } else { + \syslog($level, $message); + } + } + } +} diff --git a/inc/cache.php b/inc/cache.php index d26f9201..182d6fda 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -15,10 +15,18 @@ class Cache { switch ($config['cache']['enabled']) { case 'memcached': - return new MemcachedCacheDriver( - $config['cache']['prefix'], - $config['cache']['memcached'] - ); + $prefix = $config['cache']['prefix']; + $uri = $config['cache']['memcached'][0]; + $port = 0; + $weight = 0; + if (isset($config['cache']['memcached'][1]) && $config['cache']['memcached'][1] !== null) { + $port = \intval($config['cache']['memcached'][1]); + } + if (isset($config['cache']['memcached'][2]) && $config['cache']['memcached'][2] !== null) { + $weight = \intval($config['cache']['memcached'][2]); + } + + return new MemcachedCacheDriver($prefix, $uri, $port, $weight); case 'redis': $port = $config['cache']['redis'][1]; $port = empty($port) ? null : intval($port); diff --git a/inc/config.php b/inc/config.php index def2cd27..fb4b4493 100644 --- a/inc/config.php +++ b/inc/config.php @@ -63,9 +63,29 @@ // been generated. This keeps the script from querying the database and causing strain when not needed. $config['has_installed'] = '.installed'; - // Use syslog() for logging all error messages and unauthorized login attempts. + // Deprecated, use 'log_system'. $config['syslog'] = false; + $config['log_system'] = [ + /* + * Log all error messages and unauthorized login attempts. + * Can be "syslog", "error_log" (default), "file", or "stderr". + */ + 'type' => 'error_log', + // The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility. + 'name' => 'tinyboard', + /* + * Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. Defaults to + * false. + */ + 'syslog_stderr' => false, + /* + * Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. Defaults to + * '/var/log/vichan.log'. + */ + 'file_path' => '/var/log/vichan.log', + ]; + // Use `host` via shell_exec() to lookup hostnames, avoiding query timeouts. May not work on your system. // Requires safe_mode to be disabled. $config['dns_system'] = false; @@ -923,10 +943,6 @@ // Location of thumbnail to use for deleted images. $config['image_deleted'] = 'static/deleted.png'; - // When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use - // that as a thumbnail instead of resizing/redrawing. - $config['minimum_copy_resize'] = false; - // Maximum image upload size in bytes. $config['max_filesize'] = 10 * 1024 * 1024; // 10MB // Maximum image dimensions. @@ -965,15 +981,6 @@ // Set this to true if you're using Linux and you can execute `md5sum` binary. $config['gnu_md5'] = false; - // Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter. - $config['tesseract_ocr'] = false; - - // Tesseract parameters - $config['tesseract_params'] = ''; - - // Tesseract preprocess command - $config['tesseract_preprocess_command'] = 'convert -monochrome %s -'; - // Number of posts in a "View Last X Posts" page $config['noko50_count'] = 50; // Number of posts a thread needs before it gets a "View Last X Posts" page. @@ -1291,6 +1298,7 @@ $config['error']['pendingappeal'] = _('There is already a pending appeal for this ban.'); $config['error']['invalidpassword'] = _('Wrong password…'); $config['error']['invalidimg'] = _('Invalid image.'); + $config['error']['invalidfile'] = _('Invalid file.'); $config['error']['unknownext'] = _('Unknown file extension.'); $config['error']['filesize'] = _('Maximum file size: %maxsz% bytesYour file\'s size: %filesz% bytes'); $config['error']['maxsize'] = _('The file was too big.'); @@ -1888,45 +1896,6 @@ // Example: Adding the pre-markup post body to the API as "com_nomarkup". // $config['api']['extra_fields'] = array('body_nomarkup' => 'com_nomarkup'); -/* - * ================== - * NNTPChan settings - * ================== - */ - -/* - * Please keep in mind that NNTPChan support in vichan isn't finished yet / is in an experimental - * state. Please join #nntpchan on Rizon in order to peer with someone. - */ - - $config['nntpchan'] = array(); - - // Enable NNTPChan integration - $config['nntpchan']['enabled'] = false; - - // NNTP server - $config['nntpchan']['server'] = "localhost:1119"; - - // Global dispatch array. Add your boards to it to enable them. Please make - // sure that this setting is set in a global context. - $config['nntpchan']['dispatch'] = array(); // 'overchan.test' => 'test' - - // Trusted peer - an IP address of your NNTPChan instance. This peer will have - // increased capabilities, eg.: will evade spamfilter. - $config['nntpchan']['trusted_peer'] = '127.0.0.1'; - - // Salt for message ID generation. Keep it long and secure. - $config['nntpchan']['salt'] = 'change_me+please'; - - // A local message ID domain. Make sure to change it. - $config['nntpchan']['domain'] = 'example.vichan.net'; - - // An NNTPChan group name. - // Please set this setting in your board/config.php, not globally. - $config['nntpchan']['group'] = false; // eg. 'overchan.test' - - - /* * ==================== * Other/uncategorized @@ -2041,7 +2010,7 @@ // Password hashing method version // If set to 0, it won't upgrade hashes using old password encryption schema, only create new. // You can set it to a higher value, to further migrate to other password hashing function. - $config['password_crypt_version'] = 1; + $config['password_crypt_version'] = 2; // Use CAPTCHA for reports? $config['report_captcha'] = false; @@ -2061,9 +2030,16 @@ // Enable auto IP note generation of moderator deleted posts $config['autotagging'] = false; - // Enable PDF file thumbnail generation + // Enable PDF thumbnail generation. + // Requires a working installation of ghostscript and imagemagick. + // Imagemagick support of PDF files is not required. $config['pdf_file_thumbnail'] = false; + // Enable djvu thumbnail generation. + // Requires djvulibre's tools and imagemagick. + // Imagemagick support of djvu files is not required. + $config['djvu_file_thumbnail'] = false; + // Enable TXT file thumbnail $config['txt_file_thumbnail'] = false; diff --git a/inc/context.php b/inc/context.php index 3207d7ea..11a153ec 100644 --- a/inc/context.php +++ b/inc/context.php @@ -1,8 +1,8 @@ $config, + LogDriver::class => function($c) { + $config = $c->get('config'); + + $name = $config['log_system']['name']; + $level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE; + $backend = $config['log_system']['type']; + + $legacy_syslog = isset($config['syslog']) && $config['syslog']; + + // Check 'syslog' for backwards compatibility. + if ($legacy_syslog || $backend === 'syslog') { + $log_driver = new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']); + if ($legacy_syslog) { + $log_driver->log(LogDriver::NOTICE, 'The configuration setting \'syslog\' is deprecated. Please use \'log_system\' instead'); + } + return $log_driver; + } elseif ($backend === 'file') { + return new FileLogDriver($name, $level, $config['log_system']['file_path']); + } elseif ($backend === 'stderr') { + return new StderrLogDriver($name, $level); + } elseif ($backend === 'error_log') { + return new ErrorLogLogDriver($name, $level); + } else { + $log_driver = new ErrorLogLogDriver($name, $level); + $log_driver->log(LogDriver::ERROR, "Unknown 'log_system' value '$backend', using 'error_log' default"); + return $log_driver; + } + }, CacheDriver::class => function($c) { // Use the global for backwards compatibility. return \cache::getCache(); diff --git a/inc/database.php b/inc/database.php index f5ea73bd..4b0bb6c3 100644 --- a/inc/database.php +++ b/inc/database.php @@ -66,7 +66,7 @@ function sql_open() { $dsn = $config['db']['type'] . ':' . ($unix_socket ? 'unix_socket=' . $unix_socket : 'host=' . $config['db']['server']) . - ';dbname=' . $config['db']['database']; + ';charset=utf8mb4;dbname=' . $config['db']['database']; if (!empty($config['db']['dsn'])) $dsn .= ';' . $config['db']['dsn']; try { @@ -85,7 +85,7 @@ function sql_open() { if ($config['debug']) { $debug['time']['db_connect'] = '~' . round((microtime(true) - $start) * 1000, 2) . 'ms'; if ($config['db']['type'] == "mysql") { - query('SET NAMES utf8') or error(db_error()); + query('SET NAMES utf8mb4') or error(db_error()); } } return $pdo; diff --git a/inc/functions.php b/inc/functions.php index def00287..c9f6657e 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -11,6 +11,7 @@ if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) { $microtime_start = microtime(true); +use Vichan\Functions\Hide; use Lifo\IP\IP; // for expanding IPv6 address in DNSBL() // the user is not currently logged in as a moderator @@ -1691,7 +1692,7 @@ function checkSpam(array $extra_salt = array()) { $_hash = sha1($_hash . $extra_salt); if ($hash != $_hash) { - return true; + return true; } $query = prepare('SELECT `passed` FROM ``antispam`` WHERE `hash` = :hash'); @@ -2227,20 +2228,15 @@ function markup(&$body, $track_cites = false) { $clauses = array_unique($clauses); if ($board['uri'] != $_board) { - if (!openBoard($_board)){ - if (in_array($_board,array_keys($config['boards_alias']))){ - $_board = $config['boards_alias'][$_board]; - if (openBoard($_board)){ - - } - else { + if (!openBoard($_board)) { + if (\in_array($_board, \array_keys($config['boards_alias']))) { + $_board = $config['boards_alias'][$_board]; + if (!openBoard($_board)) { continue; // Unknown board - } - } - else { + } + } else { continue; // Unknown board } - } } @@ -2281,38 +2277,31 @@ function markup(&$body, $track_cites = false) { if ($cite) { if (isset($cited_posts[$_board][$cite])) { $link = $cited_posts[$_board][$cite]; - if (isset($original_board)){ - $replacement = '' . - '>>>/' . $original_board . '/' . $cite . + '>>>/' . $replacement_board . '/' . $cite . ''; + if ($track_cites && $config['track_cites']) { + $tracked_cites[] = [ $_board, $cite ]; } - else { - $replacement = '' . - '>>>/' . $_board . '/' . $cite . - ''; - - } - - $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0])); - $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]); - - if ($track_cites && $config['track_cites']) - $tracked_cites[] = array($_board, $cite); + } else { + $replacement = ">>>/$_board/$cite"; } - } elseif(isset($crossboard_indexes[$_board])) { + } elseif (isset($crossboard_indexes[$_board])) { $replacement = '' . '>>>/' . $_board . '/' . ''; - $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0])); - $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]); + } else { + $replacement = ">>>/$_board/$cite"; } + + $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0])); + $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]); } } @@ -2583,11 +2572,11 @@ function rrmdir($dir) { function poster_id($ip, $thread) { global $config; - if ($id = event('poster-id', $ip, $thread)) + if ($id = event('poster-id', $ip, $thread)) { return $id; + } - // Confusing, hard to brute-force, but simple algorithm - return substr(sha1(sha1($ip . $config['secure_trip_salt'] . $thread) . $config['secure_trip_salt']), 0, $config['poster_id_length']); + return \substr(Hide\secure_hash($ip . $config['secure_trip_salt'] . $thread . $config['secure_trip_salt'], false), 0, $config['poster_id_length']); } function generate_tripcode($name) { @@ -2615,7 +2604,7 @@ function generate_tripcode($name) { if (isset($config['custom_tripcode']["##{$trip}"])) $trip = $config['custom_tripcode']["##{$trip}"]; else - $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4))), -10); + $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(Hide\secure_hash($trip . $config['secure_trip_salt'], false), 0, 4))), -10); } else { if (isset($config['custom_tripcode']["#{$trip}"])) $trip = $config['custom_tripcode']["#{$trip}"]; diff --git a/inc/functions/hide.php b/inc/functions/hide.php new file mode 100644 index 00000000..bf972751 --- /dev/null +++ b/inc/functions/hide.php @@ -0,0 +1,6 @@ + 12 ]); + if ($r === false) { + throw new \RuntimeException("Could not hash password"); } - else { - $comp = crypt($test, $password); + + return [ $version, $r ]; +} + +function test_password(string $db_hash, string|int $version, string $input_password): bool { + $version = (int)$version; + if ($version < 2) { + $ok = \hash_equals($db_hash, \crypt($input_password, $db_hash)); + } else { + $pre_hash = \hash('tiger160,3', $input_password, false); + $ok = \password_verify($pre_hash, $db_hash); } - return array($version, hash_equals($password, $comp)); + return $ok; } -function generate_salt() { - return strtr(base64_encode(random_bytes(16)), '+', '.'); -} - -function login($username, $password) { - global $mod, $config; +function login(string $username, string $password): array|false { + global $mod; $query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `username` = :username"); $query->bindValue(':username', $username); - $query->execute() or error(db_error($query)); + $query->execute(); if ($user = $query->fetch(PDO::FETCH_ASSOC)) { - list($version, $ok) = test_password($user['password'], $user['version'], $password); + $ok = test_password($user['password'], $user['version'], $password); if ($ok) { - if ($config['password_crypt_version'] > $version) { + if ((int)$user['version'] < 2) { // It's time to upgrade the password hashing method! list ($user['version'], $user['password']) = crypt_password($password); $query = prepare("UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id"); $query->bindValue(':password', $user['password']); $query->bindValue(':version', $user['version']); $query->bindValue(':id', $user['id']); - $query->execute() or error(db_error($query)); + $query->execute(); } - return $mod = array( + return $mod = [ 'id' => $user['id'], 'type' => $user['type'], 'username' => $username, 'hash' => mkhash($username, $user['password']), 'boards' => explode(',', $user['boards']) - ); + ]; } } return false; } -function setCookies() { +function setCookies(): void { global $mod, $config; - if (!$mod) + if (!$mod) { error('setCookies() was called for a non-moderator!'); + } $is_https = Net\is_connection_https(); @@ -119,14 +117,14 @@ function setCookies() { time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, $is_https, $config['cookies']['httponly']); } -function destroyCookies() { +function destroyCookies(): void { global $config; $is_https = Net\is_connection_https(); // Delete the cookies setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, $is_https, true); } -function modLog($action, $_board=null) { +function modLog(string $action, ?string $_board = null): void { global $mod, $board, $config; $query = prepare("INSERT INTO ``modlogs`` VALUES (:id, :ip, :board, :time, :text)"); $query->bindValue(':id', (isset($mod['id']) ? $mod['id'] : -1), PDO::PARAM_INT); @@ -141,16 +139,18 @@ function modLog($action, $_board=null) { $query->bindValue(':board', null, PDO::PARAM_NULL); $query->execute() or error(db_error($query)); - if ($config['syslog']) + if ($config['syslog']) { _syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action); + } } -function create_pm_header() { +function create_pm_header(): mixed { global $mod, $config; if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) != false) { - if ($header === true) + if ($header === true) { return false; + } return $header; } @@ -159,26 +159,29 @@ function create_pm_header() { $query->bindValue(':id', $mod['id'], PDO::PARAM_INT); $query->execute() or error(db_error($query)); - if ($pm = $query->fetch(PDO::FETCH_ASSOC)) - $header = array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1); - else + if ($pm = $query->fetch(PDO::FETCH_ASSOC)) { + $header = [ 'id' => $pm['id'], 'waiting' => $query->rowCount() - 1 ]; + } else { $header = true; + } - if ($config['cache']['enabled']) + if ($config['cache']['enabled']) { cache::set('pm_unread_' . $mod['id'], $header); + } - if ($header === true) + if ($header === true) { return false; + } return $header; } -function make_secure_link_token($uri) { +function make_secure_link_token(string $uri): string { global $mod, $config; return substr(sha1($config['cookies']['salt'] . '-' . $uri . '-' . $mod['id']), 0, 8); } -function check_login(Context $ctx, $prompt = false) { +function check_login(Context $ctx, bool $prompt = false): void { global $config, $mod; // Validate session @@ -188,7 +191,9 @@ function check_login(Context $ctx, $prompt = false) { if (count($cookie) != 3) { // Malformed cookies destroyCookies(); - if ($prompt) mod_login($ctx); + if ($prompt) { + mod_login($ctx); + } exit; } @@ -201,7 +206,9 @@ function check_login(Context $ctx, $prompt = false) { if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) { // Malformed cookies destroyCookies(); - if ($prompt) mod_login($ctx); + if ($prompt) { + mod_login($ctx); + } exit; } diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ec33b128..17d2ef74 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -4,16 +4,19 @@ */ use Vichan\Context; use Vichan\Data\{IpNoteQueries, UserPostQueries, ReportQueries}; +use Vichan\Data\Driver\LogDriver; use Vichan\Functions\Net; defined('TINYBOARD') or exit; -function _link_or_copy(string $target, string $link): bool { - if (!link($target, $link)) { - error_log("Failed to link() $target to $link. FAlling back to copy()"); - return copy($target, $link); - } - return true; +function _link_or_copy_factory(Context $ctx): callable { + return function(string $target, string $link) use ($ctx) { + if (!\link($target, $link)) { + $ctx->get(LogDriver::class)->log(LogDriver::NOTICE, "Failed to link() $target to $link. FAlling back to copy()"); + return \copy($target, $link); + } + return true; + }; } function mod_page($title, $template, $args, $subtitle = false) { @@ -43,7 +46,7 @@ function clone_wrapped_with_exist_check($clonefn, $src, $dest) { } function mod_login(Context $ctx, $redirect = false) { - global $config; + $config = $ctx->get('config'); $args = []; @@ -54,8 +57,7 @@ function mod_login(Context $ctx, $redirect = false) { if (!isset($_POST['username'], $_POST['password']) || $_POST['username'] == '' || $_POST['password'] == '') { $args['error'] = $config['error']['invalid']; } elseif (!login($_POST['username'], $_POST['password'])) { - if ($config['syslog']) - _syslog(LOG_WARNING, 'Unauthorized login attempt!'); + $ctx->get(LogDriver::class)->log(LogDriver::INFO, 'Unauthorized login attempt!'); $args['error'] = $config['error']['invalid']; } else { @@ -87,15 +89,16 @@ function mod_confirm(Context $ctx, $request) { } function mod_logout(Context $ctx) { - global $config; + $config = $ctx->get('config'); destroyCookies(); header('Location: ?/', true, $config['redirect_http']); } function mod_dashboard(Context $ctx) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); $report_queries = $ctx->get(ReportQueries::class); $args = []; @@ -189,7 +192,7 @@ function mod_dashboard(Context $ctx) { } function mod_search_redirect(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['search'])) error($config['error']['noaccess']); @@ -469,7 +472,9 @@ function mod_edit_board(Context $ctx, $boardName) { } function mod_new_board(Context $ctx) { - global $config, $board; + global $board; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['newboard'])) error($config['error']['noaccess']); @@ -535,7 +540,9 @@ function mod_new_board(Context $ctx) { } function mod_noticeboard(Context $ctx, $page_no = 1) { - global $config, $pdo, $mod; + global $pdo, $mod; + + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -590,7 +597,7 @@ function mod_noticeboard(Context $ctx, $page_no = 1) { } function mod_noticeboard_delete(Context $ctx, $id) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['noticeboard_delete'])) error($config['error']['noaccess']); @@ -608,7 +615,9 @@ function mod_noticeboard_delete(Context $ctx, $id) { } function mod_news(Context $ctx, $page_no = 1) { - global $config, $pdo, $mod; + global $pdo, $mod; + + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -655,7 +664,7 @@ function mod_news(Context $ctx, $page_no = 1) { } function mod_news_delete(Context $ctx, $id) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['news_delete'])) error($config['error']['noaccess']); @@ -670,7 +679,7 @@ function mod_news_delete(Context $ctx, $id) { } function mod_log(Context $ctx, $page_no = 1) { - global $config; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -695,7 +704,7 @@ function mod_log(Context $ctx, $page_no = 1) { } function mod_user_log(Context $ctx, $username, $page_no = 1) { - global $config; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -732,7 +741,7 @@ function protect_ip($entry) { } function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, $public = false) { - global $config; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -800,7 +809,9 @@ function mod_view_catalog(Context $ctx, $boardName) { } function mod_view_board(Context $ctx, $boardName, $page_no = 1) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($boardName)){ require "templates/themes/overboards/overboards.php"; @@ -831,20 +842,24 @@ function mod_view_board(Context $ctx, $boardName, $page_no = 1) { } function mod_view_thread(Context $ctx, $boardName, $thread) { - global $config, $mod; + global $mod; - if (!openBoard($boardName)) + if (!openBoard($boardName)) { + $config = $ctx->get('config'); error($config['error']['noboard']); + } $page = buildThread($thread, true, $mod); echo $page; } function mod_view_thread50(Context $ctx, $boardName, $thread) { - global $config, $mod; + global $mod; - if (!openBoard($boardName)) + if (!openBoard($boardName)) { + $config = $ctx->get('config'); error($config['error']['noboard']); + } $page = buildThread50($thread, true, $mod); echo $page; @@ -1093,7 +1108,7 @@ function mod_user_posts_by_passwd(Context $ctx, string $passwd, string $encoded_ } function mod_ban(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['ban'])) error($config['error']['noaccess']); @@ -1114,7 +1129,7 @@ function mod_ban(Context $ctx) { } function mod_warning(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['warning'])) error($config['error']['noaccess']); @@ -1131,9 +1146,10 @@ function mod_warning(Context $ctx) { } function mod_bans(Context $ctx) { - global $config; global $mod; + $config = $ctx->get('config'); + if (!hasPermission($config['mod']['view_banlist'])) error($config['error']['noaccess']); @@ -1166,7 +1182,9 @@ function mod_bans(Context $ctx) { } function mod_bans_json(Context $ctx) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['ban'])) error($config['error']['noaccess']); @@ -1178,7 +1196,9 @@ function mod_bans_json(Context $ctx) { } function mod_ban_appeals(Context $ctx) { - global $config, $board; + global $board; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['view_ban_appeals'])) error($config['error']['noaccess']); @@ -1260,7 +1280,7 @@ function mod_ban_appeals(Context $ctx) { } function mod_lock(Context $ctx, $board, $unlock, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1296,7 +1316,7 @@ function mod_lock(Context $ctx, $board, $unlock, $post) { } function mod_sticky(Context $ctx, $board, $unsticky, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1320,7 +1340,7 @@ function mod_sticky(Context $ctx, $board, $unsticky, $post) { } function mod_cycle(Context $ctx, $board, $uncycle, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1342,7 +1362,7 @@ function mod_cycle(Context $ctx, $board, $uncycle, $post) { } function mod_bumplock(Context $ctx, $board, $unbumplock, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1489,8 +1509,9 @@ function mod_move(Context $ctx, $originBoard, $postID) { if ($targetBoard === $originBoard) error(_('Target and source board are the same.')); + $_link_or_copy = _link_or_copy_factory($ctx); // link() if leaving a shadow thread behind; else, rename(). - $clone = $shadow ? '_link_or_copy' : 'rename'; + $clone = $shadow ? $_link_or_copy : 'rename'; // indicate that the post is a thread $post['op'] = true; @@ -1784,7 +1805,8 @@ function mod_merge(Context $ctx, $originBoard, $postID) { $op = $post; $op['id'] = $newID; - $clone = $shadow ? '_link_or_copy' : 'rename'; + $_link_or_copy = _link_or_copy_factory($ctx); + $clone = $shadow ? $_link_or_copy : 'rename'; if ($post['has_file']) { // copy image @@ -1923,7 +1945,9 @@ function mod_merge(Context $ctx, $originBoard, $postID) { } function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2042,7 +2066,9 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { } function mod_warning_post(Context $ctx, $board, $post, $token = false) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2139,7 +2165,7 @@ function mod_warning_post(Context $ctx, $board, $post, $token = false) { } function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2216,7 +2242,9 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { } function mod_delete(Context $ctx, $board, $post) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2279,7 +2307,7 @@ function mod_delete(Context $ctx, $board, $post) { } function mod_deletefile(Context $ctx, $board, $post, $file) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2302,7 +2330,7 @@ function mod_deletefile(Context $ctx, $board, $post, $file) { } function mod_spoiler_image(Context $ctx, $board, $post, $file) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2347,8 +2375,9 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) { } function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { - global $config, $board, $mod; + global $board, $mod; + $config = $ctx->get('config'); $global = (bool)$global; if (!openBoard($boardName)) @@ -2466,7 +2495,9 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { } function mod_user(Context $ctx, $uid) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['editusers']) && !(hasPermission($config['mod']['change_password']) && $uid == $mod['id'])) error($config['error']['noaccess']); @@ -2644,7 +2675,7 @@ function mod_user_new(Context $ctx) { function mod_users(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['manageusers'])) error($config['error']['noaccess']); @@ -2665,7 +2696,7 @@ function mod_users(Context $ctx) { } function mod_user_promote(Context $ctx, $uid, $action) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['promoteusers'])) error($config['error']['noaccess']); @@ -2763,7 +2794,7 @@ function mod_pm(Context $ctx, $id, $reply = false) { } function mod_inbox(Context $ctx) { - global $config, $mod; + global $mod; $query = prepare('SELECT `unread`,``pms``.`id`, `time`, `sender`, `to`, `message`, `username` FROM ``pms`` LEFT JOIN ``mods`` ON ``mods``.`id` = `sender` WHERE `to` = :mod ORDER BY `unread` DESC, `time` DESC'); $query->bindValue(':mod', $mod['id']); @@ -2787,7 +2818,9 @@ function mod_inbox(Context $ctx) { function mod_new_pm(Context $ctx, $username) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['create_pm'])) error($config['error']['noaccess']); @@ -2835,7 +2868,9 @@ function mod_new_pm(Context $ctx, $username) { } function mod_rebuild(Context $ctx) { - global $config, $twig; + global $twig; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['rebuild'])) error($config['error']['noaccess']); @@ -2907,7 +2942,9 @@ function mod_rebuild(Context $ctx) { } function mod_reports(Context $ctx) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['reports'])) error($config['error']['noaccess']); @@ -2974,7 +3011,7 @@ function mod_reports(Context $ctx) { } function mod_report_dismiss(Context $ctx, $id, $all = false) { - global $config; + $config = $ctx->get('config'); $report_queries = $ctx->get(ReportQueries::class); $report = $report_queries->getReportById($id); @@ -3006,13 +3043,27 @@ function mod_report_dismiss(Context $ctx, $id, $all = false) { } function mod_recent_posts(Context $ctx, $lim, $board_list = false, $json = false) { - global $config, $mod, $pdo; + global $mod, $pdo; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['recent'])) error($config['error']['noaccess']); - $limit = (is_numeric($lim))? $lim : 25; - $last_time = (isset($_GET['last']) && is_numeric($_GET['last'])) ? $_GET['last'] : 0; + $limit = 25; + if (\is_numeric($lim)) { + $lim = \intval($lim); + if ($lim > 0 && $lim < 1000) { + $limit = $lim; + } + } + $last_time = 0; + if (isset($_GET['last']) && \is_numeric($_GET['last'])) { + $last = \intval($_GET['last']); + if ($last > 0) { + $last_time = $last; + } + } $mod_boards = []; $boards = listBoards(); @@ -3104,7 +3155,9 @@ function mod_recent_posts(Context $ctx, $lim, $board_list = false, $json = false } function mod_config(Context $ctx, $board_config = false) { - global $config, $mod, $board; + global $mod, $board; + + $config = $ctx->get('config'); if ($board_config && !openBoard($board_config)) error($config['error']['noboard']); @@ -3244,7 +3297,7 @@ function mod_config(Context $ctx, $board_config = false) { } function mod_themes_list(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -3278,7 +3331,7 @@ function mod_themes_list(Context $ctx) { } function mod_theme_configure(Context $ctx, $theme_name) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -3360,7 +3413,7 @@ function mod_theme_configure(Context $ctx, $theme_name) { } function mod_theme_uninstall(Context $ctx, $theme_name) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -3377,7 +3430,7 @@ function mod_theme_uninstall(Context $ctx, $theme_name) { } function mod_theme_rebuild(Context $ctx, $theme_name) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -3426,7 +3479,7 @@ function mod_delete_page_board(Context $ctx, $page = '', $board = false) { } function mod_edit_page(Context $ctx, $id) { - global $config, $mod, $board; + global $mod, $board; $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); $query->bindValue(':id', $id); @@ -3436,6 +3489,8 @@ function mod_edit_page(Context $ctx, $id) { if (!$page) error(_('Could not find the page you are trying to edit.')); + $config = $ctx->get('config'); + if (!$page['board'] && $mod['boards'][0] !== '*') error($config['error']['noaccess']); @@ -3497,11 +3552,13 @@ function mod_edit_page(Context $ctx, $id) { } function mod_pages(Context $ctx, $board = false) { - global $config, $mod, $pdo; + global $mod, $pdo; if (empty($board)) $board = false; + $config = $ctx->get('config'); + if (!$board && $mod['boards'][0] !== '*') error($config['error']['noaccess']); @@ -3622,7 +3679,7 @@ function mod_debug_recent_posts(Context $ctx) { } function mod_debug_sql(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['debug_sql'])) error($config['error']['noaccess']); diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php deleted file mode 100644 index de67a193..00000000 --- a/inc/nntpchan/nntpchan.php +++ /dev/null @@ -1,152 +0,0 @@ -"; -} - - -function gen_nntp($headers, $files) { - if (count($files) == 0) { - } - else if (count($files) == 1 && $files[0]['type'] == 'text/plain') { - $content = $files[0]['text'] . "\r\n"; - $headers['Content-Type'] = "text/plain; charset=UTF-8"; - } - else { - $boundary = sha1($headers['Message-Id']); - $content = ""; - $headers['Content-Type'] = "multipart/mixed; boundary=$boundary"; - foreach ($files as $file) { - $content .= "--$boundary\r\n"; - if (isset($file['name'])) { - $file['name'] = preg_replace('/[\r\n\0"]/', '', $file['name']); - $content .= "Content-Disposition: form-data; filename=\"$file[name]\"; name=\"attachment\"\r\n"; - } - $type = explode('/', $file['type'])[0]; - if ($type == 'text') { - $file['type'] .= '; charset=UTF-8'; - } - $content .= "Content-Type: $file[type]\r\n"; - if ($type != 'text' && $type != 'message') { - $file['text'] = base64_encode($file['text']); - $content .= "Content-Transfer-Encoding: base64\r\n"; - } - $content .= "\r\n"; - $content .= $file['text']; - $content .= "\r\n"; - } - $content .= "--$boundary--\r\n"; - - $headers['Mime-Version'] = '1.0'; - } - //$headers['Content-Length'] = strlen($content); - $headers['Date'] = date('r', $headers['Date']); - $out = ""; - foreach ($headers as $id => $val) { - $val = str_replace("\n", "\n\t", $val); - $out .= "$id: $val\r\n"; - } - $out .= "\r\n"; - $out .= $content; - return $out; -} - -function nntp_publish($msg, $id) { - global $config; - $server = $config["nntpchan"]["server"]; - $s = fsockopen("tcp://$server"); - fgets($s); - fputs($s, "MODE STREAM\r\n"); - fgets($s); - fputs($s, "TAKETHIS $id\r\n"); - fputs($s, $msg); - fputs($s, "\r\n.\r\n"); - fgets($s); - fputs($s, "QUIT\r\n"); - fclose($s); -} - -function post2nntp($post, $msgid) { - global $config; - - $headers = array(); - $files = array(); - - $headers['Message-Id'] = $msgid; - $headers['Newsgroups'] = $config['nntpchan']['group']; - $headers['Date'] = time(); - $headers['Subject'] = $post['subject'] ? $post['subject'] : "None"; - $headers['From'] = $post['name'] . " "; - - if ($post['email'] == 'sage') { - $headers['X-Sage'] = true; - } - - if (!$post['op']) { - // Get muh parent - $query = prepare("SELECT `message_id` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id"); - $query->bindValue(':board', $post['board']); - $query->bindValue(':id', $post['thread']); - $query->execute() or error(db_error($query)); - - if ($result = $query->fetch(PDO::FETCH_ASSOC)) { - $headers['References'] = $result['message_id']; - } - else { - return false; // We don't have OP. Discarding. - } - } - - // Let's parse the body a bit. - $body = trim($post['body_nomarkup']); - $body = preg_replace('/\r?\n/', "\r\n", $body); - $body = preg_replace_callback('@>>(>/([a-zA-Z0-9_+-]+)/)?([0-9]+)@', function($o) use ($post) { - if ($o[1]) { - $board = $o[2]; - } - else { - $board = $post['board']; - } - $id = $o[3]; - - $query = prepare("SELECT `message_id_digest` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id"); - $query->bindValue(':board', $board); - $query->bindValue(':id', $id); - $query->execute() or error(db_error($query)); - - if ($result = $query->fetch(PDO::FETCH_ASSOC)) { - return ">>".substr($result['message_id_digest'], 0, 18); - } - else { - return $o[0]; // Should send URL imo - } - }, $body); - $body = preg_replace('/>>>>([0-9a-fA-F])+/', '>>\1', $body); - - - $files[] = array('type' => 'text/plain', 'text' => $body); - - foreach ($post['files'] as $id => $file) { - $fc = array(); - - $fc['type'] = $file['type']; - $fc['text'] = file_get_contents($file['file_path']); - $fc['name'] = $file['name']; - - $files[] = $fc; - } - - return array($headers, $files); -} diff --git a/inc/nntpchan/tests.php b/inc/nntpchan/tests.php deleted file mode 100644 index a63789d7..00000000 --- a/inc/nntpchan/tests.php +++ /dev/null @@ -1,30 +0,0 @@ - "czaks ", "Message-Id" => "<1234.0000.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None"], -[['type' => 'text/plain', 'text' => "THIS IS A NEW TEST THREAD"]]); -echo "\n@@@@ Single msg:\n"; -echo $m1 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1234.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], -[['type' => 'text/plain', 'text' => "hello world, with no image :("]]); -echo "\n@@@@ Single msg and pseudoimage:\n"; -echo $m2 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.2137.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], -[['type' => 'text/plain', 'text' => "hello world, now with an image!"], - ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"]]); -echo "\n@@@@ Single msg and two pseudoimages:\n"; -echo $m3 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1488.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], -[['type' => 'text/plain', 'text' => "hello world, now WITH TWO IMAGES!!!"], - ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"], - ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif2.gif"]]); -shoveitup($m0, "<1234.0000.".$time."@example.vichan.net>"); -sleep(1); -shoveitup($m1, "<1234.1234.".$time."@example.vichan.net>"); -sleep(1); -shoveitup($m2, "<1234.2137.".$time."@example.vichan.net>"); -shoveitup($m3, "<1234.1488.".$time."@example.vichan.net>"); - diff --git a/js/post-menu.js b/js/post-menu.js index 79cfd868..c2155c00 100644 --- a/js/post-menu.js +++ b/js/post-menu.js @@ -104,8 +104,10 @@ function buildMenu(e) { function addButton(post) { var $ele = $(post); + // Use unicode code with ascii variant selector + // https://stackoverflow.com/questions/37906969/how-to-prevent-ios-from-converting-ascii-into-emoji $ele.find('input.delete').after( - $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►') + $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}') ); } diff --git a/js/youtube.js b/js/youtube.js index 4a5a5afe..08989b5c 100644 --- a/js/youtube.js +++ b/js/youtube.js @@ -18,7 +18,7 @@ $(document).ready(function() { const ON = '[Remove]'; - const YOUTUBE = 'www.youtube.com'; + const YOUTUBE = 'www.youtube-nocookie.com'; function makeEmbedNode(embedHost, videoId, width, height) { return $(` 1) { - error("NNTPChan: We don't support multiple references"); - } - - $ref = $refs[0]; - - $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id` = :ref"); - $query->bindValue(':ref', $ref); - $query->execute() or error(db_error($query)); - - $ary = $query->fetchAll(PDO::FETCH_ASSOC); - - if (count($ary) == 0) { - error("NNTPChan: We don't have $ref that $msgid references"); - } - - $p_id = $ary[0]['id']; - $p_board = $ary[0]['board']; - - if ($p_board != $xboard) { - error("NNTPChan: Cross board references not allowed. Tried to reference $p_board on $xboard"); - } - - $_POST['thread'] = $p_id; - } - - $date = isset($_GET['Date']) ? strtotime($_GET['Date']) : time(); - - list($ct) = explode('; ', $_GET['Content-Type']); - - $query = prepare("SELECT COUNT(*) AS `c` FROM ``nntp_references`` WHERE `message_id` = :msgid"); - $query->bindValue(":msgid", $msgid); - $query->execute() or error(db_error($query)); - - $a = $query->fetch(PDO::FETCH_ASSOC); - if ($a['c'] > 0) { - error("NNTPChan: We already have this post. Post discarded."); - } - - if ($ct == 'text/plain') { - $content = file_get_contents("php://input"); - } elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') { - _syslog(LOG_INFO, "MM: Files: " . print_r($GLOBALS, true)); // Debug - - $content = ''; - - $newfiles = []; - foreach ($_FILES['attachment']['error'] as $id => $error) { - if ($_FILES['attachment']['type'][$id] == 'text/plain') { - $content .= file_get_contents($_FILES['attachment']['tmp_name'][$id]); - } elseif ($_FILES['attachment']['type'][$id] == 'message/rfc822') { - // Signed message, ignore for now - } else { - // A real attachment :^) - $file = []; - $file['name'] = $_FILES['attachment']['name'][$id]; - $file['type'] = $_FILES['attachment']['type'][$id]; - $file['size'] = $_FILES['attachment']['size'][$id]; - $file['tmp_name'] = $_FILES['attachment']['tmp_name'][$id]; - $file['error'] = $_FILES['attachment']['error'][$id]; - - $newfiles["file$id"] = $file; - } - } - - $_FILES = $newfiles; - } else { - error("NNTPChan: Wrong mime type: $ct"); - } - - $_POST['subject'] = isset($_GET['Subject']) ? ($_GET['Subject'] == 'None' ? '' : $_GET['Subject']) : ''; - $_POST['board'] = $xboard; - - if (isset($_GET['From'])) { - list($name, $mail) = explode(" <", $_GET['From'], 2); - $mail = preg_replace('/>\s+$/', '', $mail); - - $_POST['name'] = $name; - //$_POST['email'] = $mail; - $_POST['email'] = ''; - } - - if (isset($_GET['X_Sage'])) { - $_POST['email'] = 'sage'; - } - - $content = preg_replace_callback('/>>([0-9a-fA-F]{6,})/', function ($id) use ($xboard) { - $id = $id[1]; - - $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id_digest` LIKE :rule"); - $idx = $id . "%"; - $query->bindValue(':rule', $idx); - $query->execute() or error(db_error($query)); - - $ary = $query->fetchAll(PDO::FETCH_ASSOC); - if (count($ary) == 0) { - return ">>>>$id"; - } else { - $ret = []; - foreach ($ary as $v) { - if ($v['board'] != $xboard) { - $ret[] = ">>>/" . $v['board'] . "/" . $v['id']; - } else { - $ret[] = ">>" . $v['id']; - } - } - return implode($ret, ", "); - } - }, $content); - - $_POST['body'] = $content; - - $dropped_post = array( - 'date' => $date, - 'board' => $xboard, - 'msgid' => $msgid, - 'headers' => $headers, - 'from_nntp' => true, - ); -} - function handle_delete(Context $ctx) { // Delete @@ -610,8 +451,8 @@ function handle_delete(Context $ctx) modLog("User at $ip deleted his own post #$id"); } - _syslog( - LOG_INFO, + $ctx->get(LogDriver::class)->log( + LogDriver::INFO, 'Deleted post: ' . '/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $id) . ($post['thread'] ? '#' . $id : '') ); @@ -699,9 +540,7 @@ function handle_report(Context $ctx) foreach ($report as $id) { $post = db_select_post_minimal($board['uri'], $id); if ($post === false) { - if ($config['syslog']) { - _syslog(LOG_INFO, "Failed to report non-existing post #{$id} in {$board['dir']}"); - } + $ctx->get(LogDriver::class)->log(LogDriver::INFO, "Failed to report non-existing post #{$id} in {$board['dir']}"); error($config['error']['nopost']); } @@ -716,13 +555,12 @@ function handle_report(Context $ctx) error($error); } - if ($config['syslog']) - _syslog( - LOG_INFO, - 'Reported post: ' . - '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') . - ' for "' . $reason . '"' - ); + $ctx->get(LogDriver::class)->log( + LogDriver::INFO, + 'Reported post: ' . + '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') . + ' for "' . $reason . '"' + ); $report_queries->add($_SERVER['REMOTE_ADDR'], $board['uri'], $id, $reason); @@ -791,9 +629,9 @@ function handle_report(Context $ctx) function handle_post(Context $ctx) { - global $config, $dropped_post, $board, $mod, $pdo; + global $config, $board, $mod, $pdo; - if (!isset($_POST['body'], $_POST['board']) && !$dropped_post) { + if (!isset($_POST['body'], $_POST['board'])) { error($config['error']['bot']); } @@ -836,108 +674,104 @@ function handle_post(Context $ctx) } - if (!$dropped_post) { - // Check for CAPTCHA right after opening the board so the "return" link is in there. - if ($config['captcha']['mode'] !== false) { - if (!isset($_POST['captcha-response'], $_POST['captcha-form-id'])) { - error($config['error']['bot']); - } - - $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; - $ret = check_captcha($config['captcha'], $_POST['captcha-form-id'], $post['board'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $expected_action); - if (!$ret) { - error($config['error']['captcha']); - } - } - - if (isset($config['simple_spam']) && $post['op']) { - if (!isset($_POST['simple_spam']) || $config['simple_spam']['answer'] != $_POST['simple_spam']) { - error($config['error']['simple_spam']); - } - } - - if (isset($config['securimage']) && $config['securimage']) { - if (!isset($_POST['captcha'])) { - error($config['error']['securimage']['missing']); - } - - if (empty($_POST['captcha'])) { - error($config['error']['securimage']['empty']); - } - - if (!db_delete_captcha($_SERVER['REMOTE_ADDR'], $_POST['captcha'])) { - error($config['error']['securimage']['bad']); - } - } - - if ( - !(($post['op'] && $_POST['post'] == $config['button_newtopic']) || - (!$post['op'] && $_POST['post'] == $config['button_reply'])) - ) { + // Check for CAPTCHA right after opening the board so the "return" link is in there. + if ($config['captcha']['mode'] !== false) { + if (!isset($_POST['captcha-response'], $_POST['captcha-form-id'])) { error($config['error']['bot']); } - // Check the referrer - if ( - $config['referer_match'] !== false && - (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))) - ) { - error($config['error']['referer']); + $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; + $ret = check_captcha($config['captcha'], $_POST['captcha-form-id'], $post['board'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $expected_action); + if (!$ret) { + error($config['error']['captcha']); + } + } + + if (isset($config['simple_spam']) && $post['op']) { + if (!isset($_POST['simple_spam']) || $config['simple_spam']['answer'] != $_POST['simple_spam']) { + error($config['error']['simple_spam']); + } + } + + if (isset($config['securimage']) && $config['securimage']) { + if (!isset($_POST['captcha'])) { + error($config['error']['securimage']['missing']); } - checkDNSBL(); - - // Check if banned - checkBan($board['uri']); - - if ($config['op_require_history'] && $post['op'] && !isIPv6()) { - $has_any = has_any_history($_SERVER['REMOTE_ADDR'], $_POST['password']); - if (!$has_any) { - error($config['error']['opnohistory']); - } + if (empty($_POST['captcha'])) { + error($config['error']['securimage']['empty']); } - if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { - check_login($ctx, false); - if (!$mod) { - // Liar. You're not a mod >:-[ - error($config['error']['notamod']); - } + if (!db_delete_captcha($_SERVER['REMOTE_ADDR'], $_POST['captcha'])) { + error($config['error']['securimage']['bad']); + } + } - $post['sticky'] = $post['op'] && isset($_POST['sticky']); - $post['locked'] = $post['op'] && isset($_POST['lock']); - $post['raw'] = isset($_POST['raw']); + if ( + !(($post['op'] && $_POST['post'] == $config['button_newtopic']) || + (!$post['op'] && $_POST['post'] == $config['button_reply'])) + ) { + error($config['error']['bot']); + } - if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) { - error($config['error']['noaccess']); - } - if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) { - error($config['error']['noaccess']); - } - if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) { - error($config['error']['noaccess']); - } + // Check the referrer + if ( + $config['referer_match'] !== false && + (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))) + ) { + error($config['error']['referer']); + } + + checkDNSBL(); + + // Check if banned + checkBan($board['uri']); + + if ($config['op_require_history'] && $post['op'] && !isIPv6()) { + $has_any = has_any_history($_SERVER['REMOTE_ADDR'], $_POST['password']); + if (!$has_any) { + error($config['error']['opnohistory']); + } + } + + if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { + check_login($ctx, false); + if (!$mod) { + // Liar. You're not a mod >:-[ + error($config['error']['notamod']); } - if (!$post['mod'] && $config['spam']['enabled'] == true) { - $post['antispam_hash'] = checkSpam( - array( - $board['uri'], - isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int) $_POST['page'] : null) - ) - ); - //$post['antispam_hash'] = checkSpam(); + $post['sticky'] = $post['op'] && isset($_POST['sticky']); + $post['locked'] = $post['op'] && isset($_POST['lock']); + $post['raw'] = isset($_POST['raw']); - if ($post['antispam_hash'] === true) { - error($config['error']['spam']); - } + if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) { + error($config['error']['noaccess']); } + if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) { + error($config['error']['noaccess']); + } + if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) { + error($config['error']['noaccess']); + } + } - if ($config['robot_enable'] && $config['robot_mute']) { - checkMute(); + if (!$post['mod'] && $config['spam']['enabled'] == true) { + $post['antispam_hash'] = checkSpam( + array( + $board['uri'], + isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int) $_POST['page'] : null) + ) + ); + //$post['antispam_hash'] = checkSpam(); + + if ($post['antispam_hash'] === true) { + error($config['error']['spam']); } - } else { - $mod = $post['mod'] = false; + } + + if ($config['robot_enable'] && $config['robot_mute']) { + checkMute(); } // Check if thread exists. @@ -1023,40 +857,35 @@ function handle_post(Context $ctx) $post['password'] = hashPassword($_POST['password']); $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || count($_FILES) > 0)); - if (!$dropped_post) { - if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { - $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); - if ($stripped_whitespace == '') { - error($config['error']['tooshort_body']); - } - } - - if (!$post['op']) { - // Check if thread is locked - // but allow mods to post - if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) { - error($config['error']['locked']); - } - - $numposts = numPosts($post['thread']); - - $replythreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['replies'] - 1 : $numposts['replies']; - $imagethreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['images'] - 1 : $numposts['images']; - - if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $replythreshold) { - error($config['error']['reply_hard_limit']); - } - - if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $imagethreshold) { - error($config['error']['image_hard_limit']); - } - } - } else { - if (!$post['op']) { - $numposts = numPosts($post['thread']); + if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { + $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); + if ($stripped_whitespace == '') { + error($config['error']['tooshort_body']); } } + if (!$post['op']) { + // Check if thread is locked + // but allow mods to post + if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) { + error($config['error']['locked']); + } + + $numposts = numPosts($post['thread']); + + $replythreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['replies'] - 1 : $numposts['replies']; + $imagethreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['images'] - 1 : $numposts['images']; + + if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $replythreshold) { + error($config['error']['reply_hard_limit']); + } + + if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $imagethreshold) { + error($config['error']['image_hard_limit']); + } + } + + if ($post['has_file']) { // Determine size sanity $size = 0; @@ -1162,18 +991,16 @@ function handle_post(Context $ctx) $post['has_file'] = false; } - if (!$dropped_post) { - // Check for a file - if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { - if (!$post['has_file'] && $config['force_image_op']) { - error($config['error']['noimage']); - } + // Check for a file + if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { + if (!$post['has_file'] && $config['force_image_op']) { + error($config['error']['noimage']); } + } - // Check for too many files - if (sizeof($post['files']) > $config['max_images']) { - error($config['error']['toomanyimages']); - } + // Check for too many files + if (sizeof($post['files']) > $config['max_images']) { + error($config['error']['toomanyimages']); } if ($config['strip_combining_chars']) { @@ -1183,33 +1010,31 @@ function handle_post(Context $ctx) $post['body'] = strip_combining_chars($post['body']); } - if (!$dropped_post) { - // Check string lengths - if (mb_strlen($post['name']) > 35) { - error(sprintf($config['error']['toolong'], 'name')); - } - if (mb_strlen($post['email']) > 40) { - error(sprintf($config['error']['toolong'], 'email')); - } - if (mb_strlen($post['subject']) > 100) { - error(sprintf($config['error']['toolong'], 'subject')); - } - if (!$mod) { - $body_mb_len = mb_strlen($post['body']); - $is_op = $post['op']; + // Check string lengths + if (mb_strlen($post['name']) > 35) { + error(sprintf($config['error']['toolong'], 'name')); + } + if (mb_strlen($post['email']) > 40) { + error(sprintf($config['error']['toolong'], 'email')); + } + if (mb_strlen($post['subject']) > 100) { + error(sprintf($config['error']['toolong'], 'subject')); + } + if (!$mod) { + $body_mb_len = mb_strlen($post['body']); + $is_op = $post['op']; - if (($is_op && $config['force_body_op']) || (!$is_op && $config['force_body'])) { - $min_body = $is_op ? $config['min_body_op'] : $config['min_body']; + if (($is_op && $config['force_body_op']) || (!$is_op && $config['force_body'])) { + $min_body = $is_op ? $config['min_body_op'] : $config['min_body']; - if ($body_mb_len < $min_body) { - error($config['error']['tooshort_body']); - } + if ($body_mb_len < $min_body) { + error($config['error']['tooshort_body']); } + } - $max_body = $is_op ? $config['max_body_op'] : $config['max_body']; - if ($body_mb_len > $max_body) { - error($config['error']['toolong_body']); - } + $max_body = $is_op ? $config['max_body_op'] : $config['max_body']; + if ($body_mb_len > $max_body) { + error($config['error']['toolong_body']); } } @@ -1221,33 +1046,32 @@ function handle_post(Context $ctx) $post['body'] .= "\n1"; } - if (!$dropped_post) - if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) { - $gi = geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD); + if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) { + $gi = geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD); - function ipv4to6($ip) - { - if (strpos($ip, ':') !== false) { - if (strpos($ip, '.') > 0) { - $ip = substr($ip, strrpos($ip, ':') + 1); - } else { - // Native ipv6. - return $ip; - } + function ipv4to6($ip) + { + if (strpos($ip, ':') !== false) { + if (strpos($ip, '.') > 0) { + $ip = substr($ip, strrpos($ip, ':') + 1); + } else { + // Native ipv6. + return $ip; } - $iparr = array_pad(explode('.', $ip), 4, 0); - $part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16); - $part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16); - return '::ffff:' . $part7 . ':' . $part8; } + $iparr = array_pad(explode('.', $ip), 4, 0); + $part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16); + $part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16); + return '::ffff:' . $part7 . ':' . $part8; + } - if ($country_code = geoip_country_code_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))) { - if (!in_array(strtolower($country_code), array('eu', 'ap', 'o1', 'a1', 'a2'))) { - $post['body'] .= "\n" . strtolower($country_code) . "" . - "\n" . geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR'])) . ""; - } + if ($country_code = geoip_country_code_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))) { + if (!in_array(strtolower($country_code), array('eu', 'ap', 'o1', 'a1', 'a2'))) { + $post['body'] .= "\n" . strtolower($country_code) . "" . + "\n" . geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR'])) . ""; } } + } if ($config['user_flag'] && isset($_POST['user_flag'])) if (!empty($_POST['user_flag'])) { @@ -1267,11 +1091,9 @@ function handle_post(Context $ctx) $post['body'] .= "\n" . $_POST['tag'] . ""; } - if (!$dropped_post) { - if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']); - $post['body'] .= "\n" . $proxy . ""; - } + if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']); + $post['body'] .= "\n" . $proxy . ""; } $post['body_nomarkup'] = $post['body']; @@ -1313,7 +1135,7 @@ function handle_post(Context $ctx) } } - if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { + if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) { require_once 'inc/filters.php'; do_filters($ctx, $post); } @@ -1404,13 +1226,13 @@ function handle_post(Context $ctx) $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } elseif ( - $config['minimum_copy_resize'] && + (($config['strip_exif'] && isset($file['exif_stripped']) && $file['exif_stripped']) || !$config['strip_exif']) && $image->size->width <= $config['thumb_width'] && $image->size->height <= $config['thumb_height'] && $file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']) ) { // Copy, because there's nothing to resize - coopy($file['tmp_name'], $file['thumb']); + copy($file['tmp_name'], $file['thumb']); $file['thumbwidth'] = $image->size->width; $file['thumbheight'] = $image->size->height; @@ -1449,17 +1271,35 @@ function handle_post(Context $ctx) } $image->destroy(); } else { - if ( - ($file['extension'] == "pdf" && $config['pdf_file_thumbnail']) || - ($file['extension'] == "djvu" && $config['djvu_file_thumbnail']) - ) { - $path = $file['thumb']; - $error = shell_exec_error('convert -size ' . $config['thumb_width'] . 'x' . $config['thumb_height'] . ' -thumbnail ' . $config['thumb_width'] . 'x' . $config['thumb_height'] . ' -background white -alpha remove ' . - escapeshellarg($file['tmp_name'] . '[0]') . ' ' . - escapeshellarg($file['thumb'])); + $mime = \mime_content_type($file['tmp_name']); + $pdf = $file['extension'] === "pdf" && $config['pdf_file_thumbnail']; + $djvu = $file['extension'] === "djvu" && $config['djvu_file_thumbnail']; + + if ($pdf || $djvu) { + $e_thumb_path = \escapeshellarg($file['thumb']); + $e_file_path = \escapeshellarg($file['tmp_name']); + $thumb_width = $config['thumb_width']; + $thumb_height = $config['thumb_height']; + + // Generates a PPM image and pipes it directly into convert for resizing + type conversion. + if ($pdf && $mime === 'application/pdf') { + $error = shell_exec_error("gs -dSAFER -dBATCH -dNOPAUSE -dQUIET \ + -sDEVICE=ppmraw -r100 -dFirstPage=1 -dLastPage=1 -sOutputFile=- $e_file_path \ + | convert -thumbnail {$thumb_width}x{$thumb_height} ppm:- $e_thumb_path"); + } elseif ($djvu && $mime === 'image/vnd.djvu') { + $error = shell_exec_error("ddjvu -format=ppm -page 1 $e_file_path \ + | convert -thumbnail {$thumb_width}x{$thumb_height} ppm:- $e_thumb_path"); + } else { + // Mime check failed. + error($config['error']['invalidfile']); + } if ($error) { - $path = sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']); + $log = $ctx->get(LogDriver::class); + $log->log(LogDriver::ERROR, 'Could not render thumbnail for PDF/DJVU file, using static fallback.'); + $path = \sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']); + } else { + $path = $file['thumb']; } $file['thumb'] = basename($file['thumb']); @@ -1553,35 +1393,6 @@ function handle_post(Context $ctx) } } - if ($config['tesseract_ocr'] && $file['thumb'] != 'file') { - // Let's OCR it! - $fname = $file['tmp_name']; - - if ($file['height'] > 500 || $file['width'] > 500) { - $fname = $file['thumb']; - } - - if ($fname == 'spoiler') { - // We don't have that much CPU time, do we? - } else { - $tmpname = __DIR__ . "/tmp/tesseract/" . rand(0, 10000000); - - // Preprocess command is an ImageMagick b/w quantization - $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " . - 'tesseract stdin ' . escapeshellarg($tmpname) . ' ' . $config['tesseract_params']); - $tmpname .= ".txt"; - - $value = @file_get_contents($tmpname); - @unlink($tmpname); - - if ($value && trim($value)) { - // This one has an effect, that the body is appended to a post body. So you can write a correct - // spamfilter. - $post['body_nomarkup'] .= "" . htmlspecialchars($value) . ""; - } - } - } - if (!isset($dont_copy_file) || !$dont_copy_file) { if (isset($file['file_tmp'])) { if (!@rename($file['tmp_name'], $file['file'])) { @@ -1629,12 +1440,7 @@ function handle_post(Context $ctx) } } - // Do filters again if OCRing - if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { - do_filters($ctx, $post); - } - - if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) { + if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { undoImage($post); if ($config['robot_mute']) { error(sprintf($config['error']['muted'], mute())); @@ -1680,41 +1486,6 @@ function handle_post(Context $ctx) $post['id'] = $id = post($post); $post['slug'] = slugify($post); - if ($dropped_post && $dropped_post['from_nntp']) { - $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES " . - "(:board , :id , :message_id , :message_id_digest , false, :headers)"); - - $query->bindValue(':board', $dropped_post['board']); - $query->bindValue(':id', $id); - $query->bindValue(':message_id', $dropped_post['msgid']); - $query->bindValue(':message_id_digest', sha1($dropped_post['msgid'])); - $query->bindValue(':headers', $dropped_post['headers']); - $query->execute() or error(db_error($query)); - } // ^^^^^ For inbound posts ^^^^^ - elseif ($config['nntpchan']['enabled'] && $config['nntpchan']['group']) { - // vvvvv For outbound posts vvvvv - - require_once('inc/nntpchan/nntpchan.php'); - $msgid = gen_msgid($post['board'], $post['id']); - - list($headers, $files) = post2nntp($post, $msgid); - - $message = gen_nntp($headers, $files); - - $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES " . - "(:board , :id , :message_id , :message_id_digest , true , :headers)"); - - $query->bindValue(':board', $post['board']); - $query->bindValue(':id', $post['id']); - $query->bindValue(':message_id', $msgid); - $query->bindValue(':message_id_digest', sha1($msgid)); - $query->bindValue(':headers', json_encode($headers)); - $query->execute() or error(db_error($query)); - - // Let's broadcast it! - nntp_publish($message, $msgid); - } - insertFloodPost($post); // Handle cyclical threads @@ -1783,10 +1554,10 @@ function handle_post(Context $ctx) buildThread($post['op'] ? $id : $post['thread']); - if ($config['syslog']) { - _syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] . - link_for($post) . (!$post['op'] ? '#' . $id : '')); - } + $ctx->get(LogDriver::class)->log( + LogDriver::INFO, + 'New post: /' . $board['dir'] . $config['dir']['res'] . link_for($post) . (!$post['op'] ? '#' . $id : '') + ); if (!$post['mod']) { header('X-Associated-Content: "' . $redirect . '"'); @@ -1879,22 +1650,13 @@ function handle_appeal(Context $ctx) displayBan($ban); } -// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post. -if (isset($_GET['Newsgroups'])) { - if ($config['nntpchan']['enabled']) { - handle_nntpchan(); - } else { - error("NNTPChan: NNTPChan support is disabled"); - } -} - $ctx = Vichan\build_context($config); if (isset($_POST['delete'])) { handle_delete($ctx); } elseif (isset($_POST['report'])) { handle_report($ctx); -} elseif (isset($_POST['post']) || $dropped_post) { +} elseif (isset($_POST['post'])) { handle_post($ctx); } elseif (isset($_POST['appeal'])) { handle_appeal($ctx); diff --git a/search.php b/search.php index 0d64ed04..bfd4b022 100644 --- a/search.php +++ b/search.php @@ -1,174 +1,178 @@ $boards, 'board' => isset($_GET['board']) ? $_GET['board'] : false, 'search' => isset($_GET['search']) ? str_replace('"', '"', utf8tohtml($_GET['search'])) : false)); + +if (isset($_GET['search']) && !empty($_GET['search']) && isset($_GET['board']) && in_array($_GET['board'], $boards)) { + $phrase = $_GET['search']; + $_body = ''; + + $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `ip` = :ip AND `time` > :time"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':time', time() - ($queries_per_minutes[1] * 60)); + $query->execute() or error(db_error($query)); + if ($query->fetchColumn() > $queries_per_minutes[0]) + error(_('Wait a while before searching again, please.')); + + $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `time` > :time"); + $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); + $query->execute() or error(db_error($query)); + if ($query->fetchColumn() > $queries_per_minutes_all[0]) + error(_('Wait a while before searching again, please.')); + + + $query = prepare("INSERT INTO ``search_queries`` VALUES (:ip, :time, :query)"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':time', time()); + $query->bindValue(':query', $phrase); + $query->execute() or error(db_error($query)); + + _syslog(LOG_NOTICE, 'Searched /' . $_GET['board'] . '/ for "' . $phrase . '"'); + + // Cleanup search queries table + $query = prepare("DELETE FROM ``search_queries`` WHERE `time` <= :time"); + $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); + $query->execute() or error(db_error($query)); + + openBoard($_GET['board']); + + $filters = Array(); + + function search_filters($m) { + global $filters; + $name = $m[2]; + $value = isset($m[4]) ? $m[4] : $m[3]; + + if (!in_array($name, array('id', 'thread', 'subject', 'name'))) { + // unknown filter + return $m[0]; + } + + $filters[$name] = $value; + + return $m[1]; } - $queries_per_minutes = $config['search']['queries_per_minutes']; - $queries_per_minutes_all = $config['search']['queries_per_minutes_all']; - $search_limit = $config['search']['search_limit']; - - if (isset($config['search']['boards'])) { - $boards = $config['search']['boards']; + $phrase = trim(preg_replace_callback('/(^|\s)(\w+):("(.*)?"|[^\s]*)/', 'search_filters', $phrase)); + + if (!preg_match('/[^*^\s]/', $phrase) && empty($filters)) { + _syslog(LOG_WARNING, 'Query too broad.'); + $body .= '(Query too broad.)'; + echo Element('page.html', Array( + 'config'=>$config, + 'title'=>'Search', + 'body'=>$body, + )); + exit; + } + + // Escape escape character + $phrase = str_replace('!', '!!', $phrase); + + // Remove SQL wildcard + $phrase = str_replace('%', '!%', $phrase); + + // Use asterisk as wildcard to suit convention + $phrase = str_replace('*', '%', $phrase); + + // Remove `, it's used by table prefix magic + $phrase = str_replace('`', '!`', $phrase); + + $like = ''; + $match = Array(); + + // Find exact phrases + if (preg_match_all('/"(.+?)"/', $phrase, $m)) { + foreach($m[1] as &$quote) { + $phrase = str_replace("\"{$quote}\"", '', $phrase); + $match[] = $pdo->quote($quote); + } + } + + $words = explode(' ', $phrase); + foreach($words as &$word) { + if (empty($word)) { + continue; + } + $match[] = $pdo->quote($word); + } + + $like = ''; + foreach($match as &$phrase) { + if (!empty($like)) { + $like .= ' AND '; + } + $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase); + $like .= '`body` LIKE ' . $phrase . ' ESCAPE \'!\''; + } + + foreach($filters as $name => $value) { + if (!empty($like)) { + $like .= ' AND '; + } + $like .= '`' . $name . '` = '. $pdo->quote($value); + } + + $like = str_replace('%', '%%', $like); + + $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE " . $like . " ORDER BY `time` DESC LIMIT :limit", $board['uri'])); + $query->bindValue(':limit', $search_limit, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + if ($query->rowCount() == $search_limit) { + _syslog(LOG_WARNING, 'Query too broad.'); + $body .= '('._('Query too broad.').')'; + echo Element('page.html', Array( + 'config'=>$config, + 'title'=>'Search', + 'body'=>$body, + )); + exit; + } + + $temp = ''; + while ($post = $query->fetch()) { + if (!$post['thread']) { + $po = new Thread($post); + } else { + $po = new Post($post); + } + $temp .= $po->build(true) . ''; + } + + if (!empty($temp)) + $_body .= '' . + sprintf(ngettext('%d result in', '%d results in', $query->rowCount()), + $query->rowCount()) . ' ' . + sprintf($config['board_abbreviation'], $board['uri']) . ' - ' . $board['title'] . + '' . $temp . ''; + + $body .= ''; + if (!empty($_body)) { + $body .= $_body; } else { - $boards = listBoards(TRUE); + $body .= '('._('No results.').')'; } - - $body = Element('search_form.html', Array('boards' => $boards, 'board' => isset($_GET['board']) ? $_GET['board'] : false, 'search' => isset($_GET['search']) ? str_replace('"', '"', utf8tohtml($_GET['search'])) : false)); - - if(isset($_GET['search']) && !empty($_GET['search']) && isset($_GET['board']) && in_array($_GET['board'], $boards)) { - $phrase = $_GET['search']; - $_body = ''; - - $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `ip` = :ip AND `time` > :time"); - $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':time', time() - ($queries_per_minutes[1] * 60)); - $query->execute() or error(db_error($query)); - if($query->fetchColumn() > $queries_per_minutes[0]) - error(_('Wait a while before searching again, please.')); - - $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `time` > :time"); - $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); - $query->execute() or error(db_error($query)); - if($query->fetchColumn() > $queries_per_minutes_all[0]) - error(_('Wait a while before searching again, please.')); - - - $query = prepare("INSERT INTO ``search_queries`` VALUES (:ip, :time, :query)"); - $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':time', time()); - $query->bindValue(':query', $phrase); - $query->execute() or error(db_error($query)); - - _syslog(LOG_NOTICE, 'Searched /' . $_GET['board'] . '/ for "' . $phrase . '"'); +} - // Cleanup search queries table - $query = prepare("DELETE FROM ``search_queries`` WHERE `time` <= :time"); - $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); - $query->execute() or error(db_error($query)); - - openBoard($_GET['board']); - - $filters = Array(); - - function search_filters($m) { - global $filters; - $name = $m[2]; - $value = isset($m[4]) ? $m[4] : $m[3]; - - if(!in_array($name, array('id', 'thread', 'subject', 'name'))) { - // unknown filter - return $m[0]; - } - - $filters[$name] = $value; - - return $m[1]; - } - - $phrase = trim(preg_replace_callback('/(^|\s)(\w+):("(.*)?"|[^\s]*)/', 'search_filters', $phrase)); - - if(!preg_match('/[^*^\s]/', $phrase) && empty($filters)) { - _syslog(LOG_WARNING, 'Query too broad.'); - $body .= '(Query too broad.)'; - echo Element('page.html', Array( - 'config'=>$config, - 'title'=>'Search', - 'body'=>$body, - )); - exit; - } - - // Escape escape character - $phrase = str_replace('!', '!!', $phrase); - - // Remove SQL wildcard - $phrase = str_replace('%', '!%', $phrase); - - // Use asterisk as wildcard to suit convention - $phrase = str_replace('*', '%', $phrase); - - // Remove `, it's used by table prefix magic - $phrase = str_replace('`', '!`', $phrase); - - $like = ''; - $match = Array(); - - // Find exact phrases - if(preg_match_all('/"(.+?)"/', $phrase, $m)) { - foreach($m[1] as &$quote) { - $phrase = str_replace("\"{$quote}\"", '', $phrase); - $match[] = $pdo->quote($quote); - } - } - - $words = explode(' ', $phrase); - foreach($words as &$word) { - if(empty($word)) - continue; - $match[] = $pdo->quote($word); - } - - $like = ''; - foreach($match as &$phrase) { - if(!empty($like)) - $like .= ' AND '; - $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase); - $like .= '`body` LIKE ' . $phrase . ' ESCAPE \'!\''; - } - - foreach($filters as $name => $value) { - if(!empty($like)) - $like .= ' AND '; - $like .= '`' . $name . '` = '. $pdo->quote($value); - } - - $like = str_replace('%', '%%', $like); - - $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE " . $like . " ORDER BY `time` DESC LIMIT :limit", $board['uri'])); - $query->bindValue(':limit', $search_limit, PDO::PARAM_INT); - $query->execute() or error(db_error($query)); - - if($query->rowCount() == $search_limit) { - _syslog(LOG_WARNING, 'Query too broad.'); - $body .= '('._('Query too broad.').')'; - echo Element('page.html', Array( - 'config'=>$config, - 'title'=>'Search', - 'body'=>$body, - )); - exit; - } - - $temp = ''; - while($post = $query->fetch()) { - if(!$post['thread']) { - $po = new Thread($post); - } else { - $po = new Post($post); - } - $temp .= $po->build(true) . ''; - } - - if(!empty($temp)) - $_body .= '' . - sprintf(ngettext('%d result in', '%d results in', $query->rowCount()), - $query->rowCount()) . ' ' . - sprintf($config['board_abbreviation'], $board['uri']) . ' - ' . $board['title'] . - '' . $temp . ''; - - $body .= ''; - if(!empty($_body)) - $body .= $_body; - else - $body .= '('._('No results.').')'; - } - - echo Element('page.html', Array( - 'config'=>$config, - 'title'=>_('Search'), - 'body'=>'' . $body - )); +echo Element('page.html', Array( + 'config'=>$config, + 'title'=>_('Search'), + 'body'=>'' . $body +)); diff --git a/static/banners/1734708324675.jpg b/static/banners/1734708324675.jpg new file mode 100644 index 00000000..bcb43222 Binary files /dev/null and b/static/banners/1734708324675.jpg differ diff --git a/static/banners/closeted-dengoid.jpg b/static/banners/closeted-dengoid.jpg new file mode 100644 index 00000000..91b0f0fc Binary files /dev/null and b/static/banners/closeted-dengoid.jpg differ diff --git a/static/banners/just-monika-opt.webp b/static/banners/just-monika-opt.webp deleted file mode 100644 index 731f985a..00000000 Binary files a/static/banners/just-monika-opt.webp and /dev/null differ diff --git a/static/banners/thread-on-leftypol.jpg b/static/banners/thread-on-leftypol.jpg new file mode 100644 index 00000000..df5aadb6 Binary files /dev/null and b/static/banners/thread-on-leftypol.jpg differ diff --git a/static/flags/420.png b/static/flags/420.png new file mode 100644 index 00000000..019f2e7f Binary files /dev/null and b/static/flags/420.png differ diff --git a/static/flags/rodina_get.png b/static/flags/rodina_get.png new file mode 100644 index 00000000..a22169ec Binary files /dev/null and b/static/flags/rodina_get.png differ diff --git a/static/flags/rodina_lp.png b/static/flags/rodina_lp.png new file mode 100644 index 00000000..7cdd61a8 Binary files /dev/null and b/static/flags/rodina_lp.png differ diff --git a/static/flags/stiner.png b/static/flags/stirner.png similarity index 100% rename from static/flags/stiner.png rename to static/flags/stirner.png diff --git a/static/flags/tania.png b/static/flags/tania.png new file mode 100644 index 00000000..c711d149 Binary files /dev/null and b/static/flags/tania.png differ diff --git a/static/leftypol_logo.png b/static/leftypol_logo.png new file mode 100644 index 00000000..685a16e6 Binary files /dev/null and b/static/leftypol_logo.png differ diff --git a/stylesheets/dark.css b/stylesheets/dark.css index 9f3df691..c6d338f2 100644 --- a/stylesheets/dark.css +++ b/stylesheets/dark.css @@ -203,6 +203,10 @@ div.boardlist:not(.bottom) { div.report { color: #666666; } +theme-catalog div.thread:hover { + background: #555; + border-color: transparent; +} /* options.js */ #options_div, #alert_div { diff --git a/stylesheets/dark_red.css b/stylesheets/dark_red.css index 4ffe4eeb..71ca26e0 100644 --- a/stylesheets/dark_red.css +++ b/stylesheets/dark_red.css @@ -194,6 +194,10 @@ div.boardlist:not(.bottom) { div.report { color: #666666; } +.theme-catalog div.thread:hover { + background: #4f4f4f; + border-color: transparent; +} #options_div, #alert_div { background: #333333; } diff --git a/stylesheets/dark_spook.css b/stylesheets/dark_spook.css index 2ca55a20..e667cc01 100644 --- a/stylesheets/dark_spook.css +++ b/stylesheets/dark_spook.css @@ -209,6 +209,11 @@ div.boardlist:not(.bottom) { background-color: #1E1E1E; } +.theme-catalog div.thread:hover { + background: #555; + border-color: transparent; +} + div.report { color: #666666; } diff --git a/stylesheets/style.css b/stylesheets/style.css index 815e1853..bef69cca 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -380,6 +380,7 @@ form table tr td div.center { .file { float: left; + min-width: 100px; } .file:not(.multifile) .post-image { @@ -390,6 +391,10 @@ form table tr td div.center { float: none; } +.file.multifile { + margin: 0 10px 0 0; +} + .file.multifile > p { width: 0px; min-width: 100%; @@ -570,6 +575,13 @@ div.post div.body { white-space: pre-wrap; } +div.post div.body:before { + content: ""; + width: 18ch; + display: block; + overflow: hidden; +} + div.post.reply.highlighted { background: #D6BAD0; } diff --git a/stylesheets/szalet.css b/stylesheets/szalet.css index a766416e..6feb4939 100644 --- a/stylesheets/szalet.css +++ b/stylesheets/szalet.css @@ -206,3 +206,8 @@ div.pages { border: 0; background: none !important; } + +.theme-catalog div.thread:hover { + background: #583E28; + border-color: transparent; +} diff --git a/stylesheets/tsuki.css b/stylesheets/tsuki.css index 523bdb71..505aaad6 100644 --- a/stylesheets/tsuki.css +++ b/stylesheets/tsuki.css @@ -303,10 +303,6 @@ div.post.reply div.body { padding-bottom: 0.3em; } -div.post.reply.highlighted { - background: #D6BAD0; -} - div.post.reply div.body a { color: #D00; } @@ -321,12 +317,11 @@ div.post div.body { } div.post.reply { - background: #D6DAF0; + background: #111; + border: #555 1px solid; + box-shadow: 4px 4px #555; margin: 0.2em 4px; padding: 0.2em 0.3em 0.5em 0.6em; - border-width: 1px; - border-style: none solid solid none; - border-color: #B7C5D9; display: inline-block; max-width: 94%!important; @@ -692,8 +687,8 @@ pre { } .theme-catalog div.thread:hover { - background: #D6DAF0; - border-color: #B7C5D9; + background: #927C8E; + border-color: transparent; } .theme-catalog div.grid-size-vsmall img { @@ -1359,18 +1354,8 @@ a.post_no:hover { color: #32DD72 !important; text-decoration: underline overline; } -div.post.reply { - background: #111; - border: #555555 1px solid; - box-shadow: 4px 4px #555; - - @media (max-width: 48em) { - border-left-style: none; - border-right-style: none; - } -} div.post.reply.highlighted { - background: #555; + background: #927C8E; border: transparent 1px solid; @media (max-width: 48em) { diff --git a/templates/rules.html b/templates/rules.html index 9eaa3c3b..cfb8c8c8 100644 --- a/templates/rules.html +++ b/templates/rules.html @@ -62,9 +62,11 @@ Opening posts with liberalism or reactionary topics will be treated with far mor These examples are low quality posts that are considered, at best, bait, but are better described as spam. Any poster that violates this rule may be subject to a ban, and any post that violates this is subject to deletion at the discretion of moderators, if they feel that the topic may be an avenue for productive discussion. +15) In order to incentivize creativity and develop a genuine culture, non-original Wojaks, Pepes, or Groypers which are not /leftypol/ original content are considered spam and banned. + META: -15) Volunteers may remove other posts according to their own discretion which they feel do not contribute to the stated mission of /leftypol/, but they should try to adhere to the standards of the community and of their fellow moderators, and to refrain from arbitrary decisions. Where there is disagreement among moderators, the matter will be decided by informal consensus of currently active moderators. If there is still disagreement, the matter should be escalated to a formal vote. +16) Volunteers may remove other posts according to their own discretion which they feel do not contribute to the stated mission of /leftypol/, but they should try to adhere to the standards of the community and of their fellow moderators, and to refrain from arbitrary decisions. Where there is disagreement among moderators, the matter will be decided by informal consensus of currently active moderators. If there is still disagreement, the matter should be escalated to a formal vote. -16) Users have the right to question and challenge any bans or post removals, or other moderator actions, which they feel are unfair or do not live up to the spirit of the rules. This may be done in the moderation feedback threads on the various boards, on the /meta/ board, through the ban appeal feature, or in the Leftypol Matrix Congress chat, but comments should be considered and constructive, and should not devolve into polemics against the volunteers. Ultimately, the judgement of the moderation team is final. +17) Users have the right to question and challenge any bans or post removals, or other moderator actions, which they feel are unfair or do not live up to the spirit of the rules. This may be done in the moderation feedback threads on the various boards, on the /meta/ board, through the ban appeal feature, or in the Leftypol Matrix Congress chat, but comments should be considered and constructive, and should not devolve into polemics against the volunteers. Ultimately, the judgement of the moderation team is final. diff --git a/templates/themes/categories/frames.html b/templates/themes/categories/frames.html index 6b2fac38..1c4673cc 100644 --- a/templates/themes/categories/frames.html +++ b/templates/themes/categories/frames.html @@ -16,13 +16,13 @@ border-width: 2px; margin-right: 15px; } - + .introduction { grid-column: 2 / 9; grid-row: 1; width: 100%; } - + .content { grid-column: 2 / 9; grid-row: 2; @@ -35,7 +35,7 @@ gap: 20px; height: 100vh; } - + .modlog { width: 50%; text-align: left; @@ -69,7 +69,7 @@ li a.system { font-weight: bold; } - + @media (max-width:768px) { body{ display: grid; @@ -78,7 +78,7 @@ height: 100vh; width: 100%; } - + .introduction { grid-column: 1; grid-row: 1; @@ -96,17 +96,18 @@ grid-column: 1; grid-row: 3; width: 100%; + word-break: break-all; } - + .modlog { width: 100%; text-align: center; } - + table { table-layout: fixed; } - + table.modlog tr th { white-space: normal; word-wrap: break-word; diff --git a/templates/themes/categories/news.html b/templates/themes/categories/news.html index 484c4eb0..76722e41 100644 --- a/templates/themes/categories/news.html +++ b/templates/themes/categories/news.html @@ -11,7 +11,7 @@ .home-description { margin: 20px auto 0 auto; text-align: center; - max-width: 700px;" + max-width: 700px; } {{ boardlist.top }} diff --git a/templates/themes/faq/index.html b/templates/themes/faq/index.html index 6ddf872a..6877edd4 100644 --- a/templates/themes/faq/index.html +++ b/templates/themes/faq/index.html @@ -33,7 +33,7 @@ I'm new here and learned politics from memes would like to learn about leftism! Where should I start ? - There are some beginner lists in the reading thread. Meanwhile, consider asking your questions in /edu/ or in a relevant /leftypol/ thread, such as QTDDTOT. + Consider asking your questions in /edu/ or in a relevant /leftypol/ thread, such as QTDDTOT. What is the purpose of leftypol.org ? @@ -88,7 +88,7 @@ How can I suggest or submit fixes to the site ? - There is a /meta/ thread for this, and our Gitlab repo. + There is a /meta/ thread for this, and our Forgejo repo. I don't trust Tor exit nodes. Do you have an .onion site ? @@ -129,6 +129,10 @@ What are the maximum filesize for attachments ? Maximum file size in megabytes for attachments to a single post is 80MB (e.g. 5 * 16MB), as most boards support uploading 5 attachments by default. Maximum file size in pixels for images is currently set to 20000 by 20000. + + Can I have an account on your git instance? + + Create the account on Forgejo, then contact the staff via the Tech Team General thread on /meta/ to get your account approved. diff --git a/tmp/tesseract/.gitkeep b/tmp/tesseract/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tools/delete-stray-images.php b/tools/delete-stray-images.php index 36962730..baf955f8 100755 --- a/tools/delete-stray-images.php +++ b/tools/delete-stray-images.php @@ -44,20 +44,26 @@ foreach ($boards as $board) { ); foreach ($stray_src as $src) { - $stats['deleted']++; - $stats['size'] += filesize($board['uri'] . "/" . $config['dir']['img'] . $src); - if (!file_unlink($board['uri'] . "/" . $config['dir']['img'] . $src)) { - $er = error_get_last(); - die("error: " . $er['message'] . "\n"); + $p = $board['uri'] . "/" . $config['dir']['img'] . $src; + if (file_exists($p)) { + $stats['deleted']++; + $stats['size'] += filesize($p); + if (!file_unlink($p)) { + $er = error_get_last(); + die("error: " . $er['message'] . "\n"); + } } } foreach ($stray_thumb as $thumb) { - $stats['deleted']++; - $stats['size'] += filesize($board['uri'] . "/" . $config['dir']['thumb'] . $thumb); - if (!file_unlink($board['uri'] . "/" . $config['dir']['thumb'] . $thumb)) { - $er = error_get_last(); - die("error: " . $er['message'] . "\n"); + $p = $board['uri'] . "/" . $config['dir']['thumb'] . $thumb; + if (file_exists($p)) { + $stats['deleted']++; + $stats['size'] += filesize($p); + if (!file_unlink($p)) { + $er = error_get_last(); + die("error: " . $er['message'] . "\n"); + } } }
(Query too broad.)
('._('Query too broad.').')
('._('No results.').')
These examples are low quality posts that are considered, at best, bait, but are better described as spam. Any poster that violates this rule may be subject to a ban, and any post that violates this is subject to deletion at the discretion of moderators, if they feel that the topic may be an avenue for productive discussion.
15) In order to incentivize creativity and develop a genuine culture, non-original Wojaks, Pepes, or Groypers which are not /leftypol/ original content are considered spam and banned.
META:
15) Volunteers may remove other posts according to their own discretion which they feel do not contribute to the stated mission of /leftypol/, but they should try to adhere to the standards of the community and of their fellow moderators, and to refrain from arbitrary decisions. Where there is disagreement among moderators, the matter will be decided by informal consensus of currently active moderators. If there is still disagreement, the matter should be escalated to a formal vote.
16) Volunteers may remove other posts according to their own discretion which they feel do not contribute to the stated mission of /leftypol/, but they should try to adhere to the standards of the community and of their fellow moderators, and to refrain from arbitrary decisions. Where there is disagreement among moderators, the matter will be decided by informal consensus of currently active moderators. If there is still disagreement, the matter should be escalated to a formal vote.
16) Users have the right to question and challenge any bans or post removals, or other moderator actions, which they feel are unfair or do not live up to the spirit of the rules. This may be done in the moderation feedback threads on the various boards, on the /meta/ board, through the ban appeal feature, or in the Leftypol Matrix Congress chat, but comments should be considered and constructive, and should not devolve into polemics against the volunteers. Ultimately, the judgement of the moderation team is final.
17) Users have the right to question and challenge any bans or post removals, or other moderator actions, which they feel are unfair or do not live up to the spirit of the rules. This may be done in the moderation feedback threads on the various boards, on the /meta/ board, through the ban appeal feature, or in the Leftypol Matrix Congress chat, but comments should be considered and constructive, and should not devolve into polemics against the volunteers. Ultimately, the judgement of the moderation team is final.
There are some beginner lists in the reading thread. Meanwhile, consider asking your questions in /edu/ or in a relevant /leftypol/ thread, such as QTDDTOT.
Consider asking your questions in /edu/ or in a relevant /leftypol/ thread, such as QTDDTOT.
There is a /meta/ thread for this, and our Gitlab repo.
There is a /meta/ thread for this, and our Forgejo repo.
Maximum file size in megabytes for attachments to a single post is 80MB (e.g. 5 * 16MB), as most boards support uploading 5 attachments by default. Maximum file size in pixels for images is currently set to 20000 by 20000.
Create the account on Forgejo, then contact the staff via the Tech Team General thread on /meta/ to get your account approved.