$ git clone http://thingshare.ion.nu/thingshare.git
commit d7b4be78d9bace70394abc6c200b1e088fc66e6b
Author: Alicia <...>
Date:   Sat Oct 31 02:01:36 2020 +0100

    Added support for blocking users from sending messages.

diff --git a/messages.php b/messages.php
index 1025643..d0592d2 100644
--- a/messages.php
+++ b/messages.php
@@ -36,7 +36,7 @@ function getdisplayname($username)
 }
 $error='';
 $info='';
-// Resolve chain ID
+// Resolve chain ID (and username of second party)
 if(isset($path[2]) && $path[2]!='new' && $path[2]!='')
 {
   $chain=mysqli_real_escape_string($db, $path[2]);
@@ -45,22 +45,42 @@ if(isset($path[2]) && $path[2]!='new' && $path[2]!='')
   {
     $error='Message chain not found';
   }else{
-    $to=$res[($res[0]==$_SESSION['name'].'@'.DOMAIN)?1:0];
+    $toname=$res[($res[0]==$_SESSION['name'].'@'.DOMAIN)?1:0];
     $subject=$res[2];
   }
 }else{
   $chain='';
-  $to=$_POST['to'];
+  if(isset($_POST['to'])){$toname=$_POST['to'];}
+  else if(isset($_GET['to'])){$toname=$_GET['to'];}
+  else{$toname='';}
   $subject='';
 }
+// Check if we're blocking the user
+$to_esc=mysqli_real_escape_string($db, $toname);
+$res=mysqli_query($db, 'select user from userblocks where user='.(int)$_SESSION['id'].' and blocked="'.$to_esc.'" limit 1');
+$blocked=mysqli_fetch_row($res);
 // Send message
-if($error=='' && isset($_POST['msg']) && isset($_POST['subject']) && ($path[2]!='new' || isset($_POST['to'])) && checknonce())
+if($error=='' && ($path[2]!='new' || isset($_POST['to'])) && checknonce())
 {
-  $touser=explode('@', $to);
-  if(count($touser)!=2){$error='Invalid recipient';}else{
+  $touser=explode('@', $toname);
+  if(count($touser)!=2){$error=_('Invalid recipient');}
+  else if(isset($_POST['blockuser'])) // Handle blocking and unblocking
+  {
+    if($_POST['blockuser']==1)
+    {
+      mysqli_query($db, 'insert into userblocks(user, blocked) values('.(int)$_SESSION['id'].', "'.$to_esc.'")');
+      $info=sprintf(_('Blocked %s'), htmlentities($toname));
+      $blocked=true;
+    }else{
+      mysqli_query($db, 'delete from userblocks where user='.(int)$_SESSION['id'].' and blocked="'.$to_esc.'"');
+      $info=sprintf(_('Unblocked %s'), htmlentities($toname));
+      $blocked=false;
+    }
+  }
+  else if(!isset($_POST['msg']) || !isset($_POST['subject'])){$error=_('Missing message and/or subject');}
+  else if($blocked){$error=_('Cannot send messages to blocked users');}else{
   // Store in DB
   $subject=$_POST['subject'];
-  $to_esc=mysqli_real_escape_string($db, $to);
   $from=mysqli_real_escape_string($db, $_SESSION['name'].'@'.DOMAIN);
   $timestamp=mysqli_real_escape_string($db, date('Y-m-d H:i:s'));
   $subject_esc=mysqli_real_escape_string($db, $subject);
@@ -91,7 +111,7 @@ if($error=='' && isset($_POST['msg']) && isset($_POST['subject']) && ($path[2]!=
     // Update 'latest' on messages which are now old
     mysqli_query($db, 'update messages set latest=false where chain="'.$chain.'" and user='.(int)$_SESSION['id'].' and id!='.$id.' and sent<"'.$timestamp.'"');
   }
-  }} // Error checks
+  }} // Error checks (and blocking)
 }
 $messages='';
 $header='';
@@ -125,17 +145,15 @@ elseif($error=='' && $path[2]!='new') // Thread view
     $displayname=htmlentities(getdisplayname($row['sender']));
     $msg=$md->text(htmlentities($row['message']));
     $time=htmlentities($row['sent']);
-// TODO: CSS for this
     $messages.='<div class="message'.($row['msgread']?'':' message_unread').'"><div class="message_sender"><a href="'.BASEURL.'/user/'.$sender.'" title="'.$sender.'">'.$displayname.'</a> <span class="time">'.$time.'</span></div>'.$msg.'</div>';
   }
   mysqli_query($db, 'update messages set msgread=true where user='.(int)$_SESSION['id'].' and chain="'.$chain.'" order by sent asc');
-// TODO: Option to block user
 }
-if($path[2]=='new')
+if($chain=='')
 {
-  $to='<label>'._('To:').' <...></label><br />';
+  $to='<label>'._('To:').' <...></label><br />';
 }else{
-  $to=_('To:').' '.htmlentities($to).'<br />';
+  $to=_('To:').' '.htmlentities($toname).'<br />';
 }
 if($error!=''){$info='<span class="error">'.$error.'</span>';}
 include_once('head.php');
@@ -149,10 +167,19 @@ include_once('head.php');
 <?php if(isset($path[2]) && $path[2]!=''){ ?>
 <script src="<?=BASEURL?>/mdjs/mdjs.js"></script>
 <?=(($path[2]=='new')?'':'<h2>'._('Reply').'</h2>')?>
-<form method="post" action="<?=BASEURL?>/messages/<?=$path[2]?>"><?=nonce().$to?>
-  <?=_('Subject:')?> <input type="text" name="subject" value="<?=$subject?>" /><br />
-  <textarea rows="12" style="width:100%;" name="msg" onchange="document.getElementById('mdpreview').innerHTML='Markdown preview:<br />'+Mdjs.md2html(this.value.replace(/&/g,'&amp;amp;').replace(/</g,'&amp;lt;'));" onkeyup="this.onchange();"></textarea>
-  <div id="mdpreview"></div>
-  <button><?=_('Send')?></button>
+<form method="post" action="<?=BASEURL?>/messages/<?=$path[2]?>">
+  <p>
+    <?=nonce().$to?>
+    <?php if(!$blocked){ ?>
+    <?=_('Subject:')?> <input type="text" name="subject" value="<?=$subject?>" /><br />
+    <textarea rows="12" style="width:100%;" name="msg" onchange="document.getElementById('mdpreview').innerHTML='Markdown preview:<br />'+Mdjs.md2html(this.value.replace(/&/g,'&amp;amp;').replace(/</g,'&amp;lt;'));" onkeyup="this.onchange();"></textarea>
+    <div id="mdpreview"></div>
+    <button><?=_('Send')?></button>
+    <?php } ?>
+  </p>
+  <?php if($toname!=''){
+    print('<p><button name="blockuser" value="'.($blocked?'0':'1').'">'.sprintf($blocked?_('Unblock %s'):_('Block %s'), htmlentities($toname)).'</button></p>');
+  }
+  ?>
 </form>
 <?php } ?>
diff --git a/rpc.php b/rpc.php
index 08e4a7a..68997ec 100644
--- a/rpc.php
+++ b/rpc.php
@@ -201,7 +201,7 @@ function rpc_verifypost(&$peer)
   {
     die('{"error":"Missing data, signature, algorithm, or Thingshare node identifier header"}');
   }
-  $peer=$_SERVER['HTTP_X_THINGSHARE_NODE'];
+  $peer=strtolower($_SERVER['HTTP_X_THINGSHARE_NODE']);
   // TODO: Check algorithm against a whitelist? Might be good to avoid known weak hash algorithms and obscure algorithms which have avoided scrutiny
   // Get the public key
   $key=rpc_get($peer, 'rpckey');
diff --git a/rpc_messages.php b/rpc_messages.php
index 5f00957..32442f5 100644
--- a/rpc_messages.php
+++ b/rpc_messages.php
@@ -33,6 +33,12 @@ $subject=mysqli_real_escape_string($db, $obj['subject']);
 $msg=mysqli_real_escape_string($db, $obj['message']);
 $chain=mysqli_real_escape_string($db, $obj['chain']);
 $timestamp=mysqli_real_escape_string($db, date('Y-m-d H:i:s'));
+// Check that $user isn't blocking $sender
+$res=mysqli_query($db, 'select user from userblocks where user='.$user.' and blocked="'.$sender.'" limit 1');
+if(mysqli_fetch_row($res))
+{
+  die('{"error":"Communication blocked by user"}');
+}
 // Check that $user owns the chain and $sender is one of its participants (or that the chain doesn't exist yet)
 $res=mysqli_query($db, 'select sender, recipient from messages where user='.$user.', chain="'.$chain.'" limit 1');
 if($res=mysqli_fetch_row($res))
diff --git a/style.css b/style.css
index 1c8b8de..7f763f1 100644
--- a/style.css
+++ b/style.css
@@ -155,8 +155,11 @@ table {
 tr:nth-child(even) {
   background-color:#e8e8e8;
 }
+div.message:nth-child(even) {
+  background-color:#e8e8e8;
+}
 div.message_unread {
-  background-color:#d0ffd0;
+  background-color:#d0ffd0 !important;
 }
 a.highlight {
   font-weight:bold;
diff --git a/user.php b/user.php
index 16fc7c1..3851312 100644
--- a/user.php
+++ b/user.php
@@ -37,7 +37,7 @@ $things='';
 if($user[1]==DOMAIN && urldecode($user[0])==$_SESSION['name'])
 {
   $profile.='<a href="'.BASEURL.'/editprofile">'._('Edit').'</a>';
-  $things='<div class="thing thing-add"><a href="'.BASEURL.'/editthing/new">+</a></div>';
+  $things='<div class="thing thing-add"><a href="'.BASEURL.'/editthing/new" title="New thing">+</a></div>';
 }
 if(isset($userobj['banned']) && $userobj['banned'])
 {