|
@@ -0,0 +1,205 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+/*
|
|
|
+ This program is free software: you can redistribute it and/or modify
|
|
|
+ it under the terms of the GNU General Public License as published by
|
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
|
+ (at your option) any later version.
|
|
|
+
|
|
|
+ This program is distributed in the hope that it will be useful,
|
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+ GNU General Public License for more details.
|
|
|
+
|
|
|
+ You should have received a copy of the GNU General Public License
|
|
|
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
+*/
|
|
|
+
|
|
|
+// example of a post censored by mastodon.uno: https://livellosegreto.it/@xabacadabra/110662830871887169
|
|
|
+
|
|
|
+require 'lib/ght.php';
|
|
|
+require 'lib/ckratelimit.php';
|
|
|
+
|
|
|
+const SNAME='unocck';
|
|
|
+const SVERS='0.1';
|
|
|
+const SREPO='https://git.lattuga.net/pongrebio/unocck';
|
|
|
+const MULEN=1024;
|
|
|
+const INIFP='sec/conf.ini';
|
|
|
+const RLFP='sec/rl.state';
|
|
|
+
|
|
|
+header('Content-Language: en');
|
|
|
+
|
|
|
+$usname=ucfirst(SNAME);
|
|
|
+$timeout=5;
|
|
|
+$cjr=rand(0,999999);
|
|
|
+
|
|
|
+$conf=@parse_ini_file(INIFP,false,INI_SCANNER_RAW);
|
|
|
+if ($conf===false)
|
|
|
+ die('Could not open configuration file.');
|
|
|
+elseif (!isset($conf['host']) || !isset($conf['hostdesc']) || !isset($conf['token']) || !isset($conf['maintref']))
|
|
|
+ die('Configuration file is malformed.');
|
|
|
+
|
|
|
+if (file_exists(RLFP)) {
|
|
|
+ $buf=@file(RLFP,FILE_IGNORE_NEW_LINES);
|
|
|
+ if ($buf===false)
|
|
|
+ die('Could not open rate limiting state file.');
|
|
|
+ if (count($buf)!=2 || preg_match('#^\d+$#',$buf[0])!==1 || preg_match('#^\d+$#',$buf[1])!==1)
|
|
|
+ die('Malformed rate limiting state file.');
|
|
|
+ $rl=['remaining'=>$buf[0]+0,'restime'=>$buf[1]+0];
|
|
|
+} else {
|
|
|
+ $rl=['remaining'=>400,'restime'=>0];
|
|
|
+}
|
|
|
+
|
|
|
+$now=time();
|
|
|
+if ($rl['remaining']==10 && $now<=$rl['restime'])// ten to leave a margin for "many people using it"
|
|
|
+ die("Sorry, this {$usname} instance has reached rate limit on «{$conf['host']}», please wait at least ".ght($rl['restime']-$now,null,0).'.');
|
|
|
+
|
|
|
+$errors=[];
|
|
|
+
|
|
|
+if (isset($argv[1]))
|
|
|
+ $_GET=['purl'=>$argv[1]];
|
|
|
+
|
|
|
+if (isset($_GET['purl'])) {
|
|
|
+ if (strlen($_GET['purl'])>MULEN) {
|
|
|
+ $_GET['purl']='';
|
|
|
+ $errors[]='“Post URL” is too long';
|
|
|
+ }
|
|
|
+ $_GET['purl']=trim($_GET['purl']);
|
|
|
+ if ($_GET['purl']!='' && preg_match('#^https?://#',$_GET['purl'])!==1)// todo: make it better
|
|
|
+ $errors[]='“Post URL” is not a valid http(s) address';
|
|
|
+} else {
|
|
|
+ $_GET['purl']='';
|
|
|
+}
|
|
|
+
|
|
|
+if ($_GET['purl']!=='')
|
|
|
+ $purlhn=preg_replace('#^https?://([^/]+).*$#','$1',$_GET['purl']);
|
|
|
+
|
|
|
+if (count($errors)>0)
|
|
|
+ $errors='<div class="warning">There are some errors in the values you submitted<ul><li>'.implode('</li><li>',$errors).'</li></ul></div><div class="horsep"></div>';
|
|
|
+else
|
|
|
+ $errors='';
|
|
|
+
|
|
|
+echo "<!DOCTYPE HTML>
|
|
|
+<html lang=\"en\">
|
|
|
+<head>
|
|
|
+<title>{$usname}</title>
|
|
|
+<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
|
|
|
+<meta name=\"description\" content=\"A tool to check if {$conf['host']} is censoring a given post\">
|
|
|
+<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">
|
|
|
+<meta property=\"og:image\" content=\"imgs/ogimage.png\">
|
|
|
+<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-16.png\" sizes=\"16x16\">
|
|
|
+<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-32.png\" sizes=\"32x32\">
|
|
|
+<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-192.png\" sizes=\"192x192\">
|
|
|
+<link rel=\"icon\" type=\"image/png\" href=\"imgs/icon-512.png\" sizes=\"512x512\">
|
|
|
+<link rel=\"apple-touch-icon-precomposed\" href=\"imgs/icon-180.png\">
|
|
|
+<link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css?v={$cjr}\">
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<div id=\"main\">
|
|
|
+<h1>{$usname}</h1>
|
|
|
+<div class=\"normtext\">
|
|
|
+<p class=\"firstp\">Hello, this is a {$usname} instance. {$usname} is a tool to easily check if a Mastodon instance is censoring a given post. This {$usname} instance is set to check «{$conf['host']}», {$conf['hostdesc']}. It works by querying {$conf['host']}’s <a href=\"https://docs.joinmastodon.org/methods/search/#v2\">/api/v2/search</a> API endpoint with the credentials of an application defined inside a {$conf['host']} account, but to avoid false positives it does so only after it has verified that the URL you passed to it actually points to a publicly accessible ActivityPub object.</p>
|
|
|
+<p>{$usname} does not use cookies or Javascript and does not store any data about you anywhere.</p>
|
|
|
+<p>You can find {$usname}’s code <a href=\"".SREPO."\">here</a>.</p>
|
|
|
+<p>This {$usname} instance is maintained by {$conf['maintref']}.</p>
|
|
|
+</div>
|
|
|
+<div class=\"horsep\"></div>
|
|
|
+".$errors."
|
|
|
+<h2>Check</h2>
|
|
|
+<form method=\"get\" id=\"mainform\" name=\"mainform\">
|
|
|
+<div class=\"inputdiv\"><label for=\"purl\">Post URL</label><input type=\"text\" id=\"purl\" name=\"purl\" class=\"input\" placeholder=\"Example: https://mastodon.whatever.net/@user/110123412349306591\" value=\"{$_GET['purl']}\" maxlength=\"".MULEN."\" required></div>
|
|
|
+<div class=\"lastinputdiv\"><button type=\"submit\" id=\"button\" class=\"button\">Check</button></div>\n</form>\n";
|
|
|
+
|
|
|
+if ($errors=='' && $_GET['purl']!='') {
|
|
|
+ echo "<div class=\"horsep\"></div>\n";
|
|
|
+ $context=[
|
|
|
+ 'http'=>[
|
|
|
+ 'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\r\n",
|
|
|
+ 'method'=>'GET',
|
|
|
+ 'ignore_errors'=>true,
|
|
|
+ 'user_agent'=>'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
|
|
+ 'timeout'=>5
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ if (isset($conf['proxy']))
|
|
|
+ $context['http']['proxy']=$conf['proxy'];
|
|
|
+ $http_response_header=null;
|
|
|
+ $res=@file_get_contents($_GET['purl'],false,stream_context_create($context));
|
|
|
+ /*$xres=json_decode($res,true);
|
|
|
+ echo preprint($xres);*/
|
|
|
+ if (isset($http_response_header))
|
|
|
+ $hcode=gethttpcode($http_response_header);
|
|
|
+ if ($res===false || !isset($http_response_header)) {
|
|
|
+ echo "<div class=\"error\">Error: {$usname} could not connect to «{$purlhn}» and won’t proceed with the check.</div>\n";
|
|
|
+ } elseif ($hcode[0]!='2') {
|
|
|
+ if ($hcode[0]=='5')
|
|
|
+ echo "<div class=\"error\">«{$purlhn}» returned a server error. {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ elseif ($hcode[0]=='4')
|
|
|
+ echo "<div class=\"error\">Error: {$usname} could not access the “Post URL” you passed to it: probably the post visibility is not public/unlisted, or you passed a wrong URL. {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ elseif ($hcode[0]=='3')
|
|
|
+ echo "<div class=\"error\">Error: the “Post URL” you passed to {$usname} redirects, and since its programmer is lazy, {$usname} currently only accepts URLs which point to original posts (on Mastodon web you can copy a post’s original URL by opening the “three vertical dots” menu you find on every post and selecting “Copy link to post”). {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ elseif ($hcode[0]=='1')
|
|
|
+ echo "<div class=\"error\">«{$purlhn}» returned an unexpected and useless informational message. {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ else
|
|
|
+ echo "<div class=\"error\">«{$purlhn}» returned an unexpected HTTP code. {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ } elseif (null===$res=@json_decode($res,true)) {
|
|
|
+ echo "<div class=\"error\">Error: «{$purlhn}» returned data which could not be parsed as JSON (".json_last_error().': '.json_last_error_msg().").</div>\n";
|
|
|
+ } elseif (isset($res['error'])) {
|
|
|
+ echo "<div class=\"error\">Error: «{$purlhn}» returned «".htmlentities($res['error'])."». {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ } elseif (!isset($res['@context'][0]) || $res['@context'][0]!='https://www.w3.org/ns/activitystreams') {
|
|
|
+ echo "<div class=\"error\">Error: the “Post URL” you passed to {$usname} doesn’t point to an ActivityPub post. {$usname} won’t proceed with the check.</div>\n";
|
|
|
+ } else {
|
|
|
+ $context['http']['header']="Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\nAuthorization: Bearer {$conf['token']}\r\n";
|
|
|
+ $http_response_header=null;
|
|
|
+ $hhost=htmlentities($conf['host']);
|
|
|
+ $url=$conf['host'].'/api/v2/search?q='.urlencode($_GET['purl']).'&type=statuses&resolve=1&limit=1';
|
|
|
+ $hurl=htmlentities($url);
|
|
|
+ //while (true) {
|
|
|
+ $res=@file_get_contents('https://'.$url,false,stream_context_create($context));
|
|
|
+ if (isset($http_response_header))
|
|
|
+ $rl=ckratelimit($http_response_header,'echofun',true,false);
|
|
|
+ //echo preprint($rl);
|
|
|
+ if (is_array($rl) && @file_put_contents(RLFP,$rl['remaining']."\n".($rl['secstoreset']+time())."\n")===false)
|
|
|
+ echo "<div class=\"warning\">Warning: could not write to rate limit state file.</div>\n";
|
|
|
+ /*if ($rl['remaining']==0)
|
|
|
+ break;
|
|
|
+ usleep(250000);
|
|
|
+ }*/
|
|
|
+ if ($res===false) {
|
|
|
+ echo "<div class=\"error\">Error: could not connect to «{$hhost}».</div>\n";
|
|
|
+ } elseif (null===$res=@json_decode($res,true)) {
|
|
|
+ echo "<div class=\"error\">Error: «{$hurl}» returned data which could not be parsed as JSON (".json_last_error().': '.json_last_error_msg().").</div>\n";
|
|
|
+ } elseif (isset($res['error'])) {
|
|
|
+ echo "<div class=\"error\">Error: «{$hurl}» replied with this error message: «".htmlentities($res['error'])."».</div>\n";
|
|
|
+ } elseif (!isset($res['statuses'])) {
|
|
|
+ echo "<div class=\"error\">Error: «{$hurl}» returned data in an unexpected format.</div>\n";
|
|
|
+ } else {
|
|
|
+ if (isset($res['statuses'][0]['id']))
|
|
|
+ echo "<div class=\"neutral\">\nPost is not censored.\n</div>\n";
|
|
|
+ else
|
|
|
+ echo "<div class=\"neutral\">\nPost is censored.\n</div>\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+if (isset($conf['footer']))
|
|
|
+ echo "<div id=\"almfooter\">{$conf['footer']}</div>\n";
|
|
|
+
|
|
|
+echo "<div id=\"footer\"><a href=\"".SREPO."\">".SNAME." ".SVERS."</a></div>
|
|
|
+</div>
|
|
|
+</body>
|
|
|
+</html>\n";
|
|
|
+
|
|
|
+function preprint($var) {
|
|
|
+ return '<pre>'.print_r($var,true)."</pre>\n";
|
|
|
+}
|
|
|
+
|
|
|
+function gethttpcode($headers) {
|
|
|
+ return preg_replace('#^[^ ]+ (\d+).*$#','$1',$headers[0]);
|
|
|
+}
|
|
|
+
|
|
|
+function echofun($msg) {
|
|
|
+ echo $msg;
|
|
|
+}
|
|
|
+
|
|
|
+?>
|