Dup Ver Goto 📝

Simple Web-based Clipboard in PHP

To
435 lines, 1144 words, 10457 chars Page 'NetworkClipboard3' does not exist.

Having multiple machines, and needing an easy way to copy and paste between them, sometimes not even on the same LAN, I wrote a PHP backend for the database used in NetworkClipboard2 so that I could have something web-visible using a simple shared web host with a LAMP stack. The database is set up with the same SQL as before (two tables: pointers and clips).

Web Backend (PHP)

The bouncer.php contains the security code, which bails immediately with an error if the user is not authorised. There is a simple web interface (not included in this page) that uses essentially the same database PHP and dumps a simple formatted HTML page. Anything more complicated will use fetch and the backend below.

get

<?php
require_once("bouncer.php");
require_once("db.php"); # database credentials
require_once("jsonetc.php");
must_post();

$request = get_json_from_post();
if( !isset($request["name"]) ) {
  serve_error_json("noname","No name",400);
}
$name = $request["name"];
if( !preg_match('/^[A-Za-z0-9_]+$/',$name) ) {
  serve_error_json("invalidname","Invalid name",400);
}
if( !isset($request["namespace"]) ) {
  $namespace = "_";
} else {
  $namespace = $request["namespace"];
}
if( !preg_match('/^[A-Za-z0-9_]+$/',$namespace) ) {
  serve_error_json("invalidnamespace","Invlaid namespace",400);
}

$conn = new mysqli($host, $user, $pass, $db);

// Check connection
if ($conn->connect_error) {
  serve_error_json("connerror","SQL Connect Error",500);
}

$sql = "SELECT ${prefix}_pointers.clipid, ${prefix}_clips.value, ${prefix}_clips.time 
  FROM ${prefix}_pointers JOIN ${prefix}_clips 
  ON (${prefix}_pointers.clipid = ${prefix}_clips.id) 
  WHERE (
    ${prefix}_pointers.namespace = ? AND
    ${prefix}_pointers.name = ?
  )";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $namespace,$name);
$error = false;
$found = false;
$output = null;
if( !$stmt->execute() ) {
  serve_error_json("sqlerror","SQL Error",500);
}
$result = $stmt->get_result(); 
while($row = $result->fetch_assoc()) {
  $output = $row['value'];
  break;
}
$conn->close();
if( $output == null ) {
  serve_json([
    "namespace" => $namespace,
    "name" => $name,
    "message" => "not found",
  ],404);
}
serve_json([
  "namespace" => $namespace,
  "name" => $name,
  "message" => "success",
  "value" => $output
],200);

put

<?php
//ini_set('display_errors', 1);
//error_reporting(E_ALL);
require_once("bouncer.php");
require_once("db.php");
require_once("jsonetc.php");
must_post();

$append = false;
if( isset($_GET['append']) ) {
  $append = $_GET['append'] == "y";
}

$request = get_json_from_post();
if( !isset($request["name"]) ) {
  serve_error_json("noname","No name",400);
}
$name = $request["name"];
if( !preg_match('/^[A-Za-z0-9_]+$/',$name) ) {
  serve_error_json("invalidname","Invalid name",400);
}
if( !isset($request["namespace"]) ) {
  $namespace = "_";
} else {
  $namespace = $request["namespace"];
}
if( !preg_match('/^[A-Za-z0-9_]+$/',$namespace) ) {
  serve_error_json("invalidnamespace","Invlaid namespace",400);
}
if( !isset($request["value"]) ) {
  serve_error_json("novalue","No value",400);
}
$value = $request["value"];

$conn = new mysqli($host, $user, $pass, $db);

// Check connection
if ($conn->connect_error) {
  serve_error_json("connerror","SQL Connect Error",500);
}

$prev = "";
if( $append ) {
  $sql = "SELECT ${prefix}_pointers.clipid, ${prefix}_clips.value, ${prefix}_clips.time 
    FROM ${prefix}_pointers JOIN ${prefix}_clips 
    ON (${prefix}_pointers.clipid = ${prefix}_clips.id) 
    WHERE (
      ${prefix}_pointers.namespace = ? AND
      ${prefix}_pointers.name = ?
    )";
  $stmt = $conn->prepare($sql);
  $stmt->bind_param("ss", $namespace,$name);
  $output = null;
  if( !$stmt->execute() ) {
    serve_error_json("sqlerror","SQL Error",500);
  }
  $result = $stmt->get_result(); 
  while($row = $result->fetch_assoc()) {
    $output = $row['value'];
    break;
  }
  if( !is_null($output) ) {
    $prev = $output;
    if( !substr($output,-1) === "\n" ) {
      $prev .= "\n";
    }
  }
}
$value = $prev.$value;

$sql1 = $conn->prepare("START TRANSACTION");
$sql2 = $conn->prepare("INSERT INTO ${prefix}_clips 
  (namespace,name,value) VALUES ( ?, ?, ? )");
$sql2->bind_param("sss",$namespace,$name,$value);
$sql3 = $conn->prepare("SET @a = (SELECT LAST_INSERT_ID())");
$sql4 = $conn->prepare("INSERT INTO ${prefix}_pointers (namespace,name,clipid) 
  VALUES ( ?, ?, @a )
  ON DUPLICATE KEY UPDATE clipid=(@a)");
$sql4->bind_param("ss",$namespace,$name);
$sql5 = $conn->prepare("COMMIT");

if( !$sql1->execute() ) { 
  serve_error_json("sqlerror","SQL Error sql1",500);
}
if( !$sql2->execute() ) { 
  serve_error_json("sqlerror","SQL Error sql2",500);
}
if( !$sql3->execute() ) { 
  serve_error_json("sqlerror","SQL Error sql3",500);
}
if( !$sql4->execute() ) { 
  serve_error_json("sqlerror","SQL Error sql4",500);
}
if( !$sql5->execute() ) { 
  serve_error_json("sqlerror","SQL Error sql5",500);
}
$conn->close();
serve_json([
  "namespace" => $namespace,
  "name" => $name,
  "message" => "inserted"
],200);

list

<?php

require_once("bouncer.php");
require_once("db.php");
require_once("jsonetc.php");
must_post();

$request = get_json_from_post();
if( !isset($request["name"]) ) {
  serve_error_json("noname","No name",400);
}
$pattern = $request["name"];
if( preg_match('@/@',$pattern) ) {
  serve_error_json("invalidname","Invalid name",400);
}
if( !isset($request["namespace"]) ) {
  $namespace = "_";
} else {
  $namespace = $request["namespace"];
}
if( !preg_match('/^[A-Za-z0-9_]+$/',$namespace) ) {
  serve_error_json("invalidnamespace","Invlaid namespace",400);
}

$conn = new mysqli($host, $user, $pass, $db);

// Check connection
if ($conn->connect_error) {
  serve_error_json("connerror","SQL Connect Error",500);
}

$sql1 = "SELECT name FROM ${prefix}_pointers WHERE namespace = ?";
$stmt1 = $conn->prepare($sql1);
$stmt1->bind_param("s", $namespace);
$error = false;
$found = false;
$names = [];
if( !$stmt1->execute() ) {
  http_status_code(500);
  die("stmt1");
}
$result = $stmt1->get_result(); 
while($row = $result->fetch_assoc()) {
  $x = $row['name'];
  if( @preg_match("/$pattern/",$x) ) {
    array_push($names,"$namespace:$x");
  }
}
if( count($names) == 0 ) {
  serve_json([
    "namespace" => $namespace,
    "name" => $pattern,
    "message" => "not found",
  ],404);
}
serve_json([
  "namespace" => $namespace,
  "pattern" => $pattern,
  "message" => "success",
  "names" => $names
],200);

$conn->close();

CLI Frontend (Python)

Very quick and dirty, certainly the antithesis of proper software engineering, but does everything I need, so here it is.

eget

#!/usr/bin/env python
import sys
import os
import requests
import bouncer
import json
from icecream import ic

url = "..."

def eprint(*xs,**kw):
  kw['file'] = sys.stderr
  print(*xs,**kw)

args = sys.argv[1:]
if len(args) == 0:
  eprint(f"{sys.argv[0]} <name> [<name>...]")
  exit(1)

def getname(arg):
  if ":" in arg:
    namespace, name = arg.split(":",1)
  else:
    name = arg
    namespace = os.getenv("CLIPNS","_")
  return (namespace,name)

cookies = { bouncer.cookiename: bouncer.cookievalue }

for arg in args:
  namespace, name = getname(arg)
  req = { "namespace": namespace, "name": name }
  j = json.dumps(req)

  r = requests.post(url,data=j,cookies=cookies)
  if r.status_code == 200:
    try:
      b = json.loads(r.text)
      value = b["value"]
      value = value.replace("\r","")
      print(value)
    except Exception as e:
      eprint("error",r,r.text,e,file=sys.stderr)
  if r.status_code == 404:
    eprint(f"{namespace}:{name} not found",file=sys.stderr)
    exit(1)
  if r.status_code == 403:
    eprint(f"Access denied",file=sys.stderr)
    exit(1)

eput

#!/usr/bin/env python
import sys
import os
import requests
import bouncer
import json
from icecream import ic

url = "..."

def eprint(*xs,**kw):
  kw['file'] = sys.stderr
  print(*xs,**kw)

args = sys.argv[1:]
if len(args) == 0:
  eprint(f"{sys.argv[0]} <name> [<filename>...]")
  eprint(f"Uses standard input for -, or if no filenames provided")
  exit(1)

def getname(arg):
  if ":" in arg:
    namespace, name = arg.split(":",1)
  else:
    name = arg
    namespace = os.getenv("CLIPNS","_")
  return (namespace,name)

cookies = { bouncer.cookiename: bouncer.cookievalue }

namespace, name = getname(args.pop(0))
if len(args) == 0:
  args = ["-"]
value = []

for arg in args:
  if arg == "-":
    value.append(sys.stdin.read())
  else:
    if os.path.isfile(arg):
      with open(arg) as f:
        value.append(f.read())
    else:
      eprint(f"{arg} is not a file")
if len(value) == 0:
  eprint(f"No content")
value = "\n".join(value)
value = value.replace("\r","")

req = { "namespace": namespace, "name": name, "value": value }
j = json.dumps(req)

r = requests.post(url,data=j,cookies=cookies)
if r.status_code == 200:
  eprint(f"Inserted {namespace}:{name} ({len(value)} bytes)")
elif r.status_code == 400:
  eprint(f"Invalid request",file=sys.stderr)
  exit(1)
elif r.status_code == 500:
  eprint(f"Server error",file=sys.stderr)
  eprint(r.text,file=sys.stderr)
  exit(1)
elif r.status_code == 403:
  eprint(f"Access denied",file=sys.stderr)
  exit(1)
else:
  eprint("WTF",r.status_code,r.text)
  exit(1)

elist

#!/usr/bin/env python
import sys
import os
import requests
import bouncer
import json
from icecream import ic

url = "..."

def eprint(*xs,**kw):
  kw['file'] = sys.stderr
  print(*xs,**kw)

args = sys.argv[1:]
if len(args) == 0:
  eprint(f"{sys.argv[0]} <name> [<name>...]")
  exit(1)

def getname(arg):
  if ":" in arg:
    namespace, name = arg.split(":",1)
  else:
    name = arg
    namespace = os.getenv("CLIPNS","_")
  return (namespace,name)

cookies = { bouncer.cookiename: bouncer.cookievalue }

for arg in args:
  namespace, name = getname(arg)
  req = { "namespace": namespace, "name": name }
  j = json.dumps(req)

  r = requests.post(url,data=j,cookies=cookies)
  if r.status_code == 200:
    try:
      b = json.loads(r.text)
      if b["message"] == "success":
        print("\n".join(b["names"]))
      else:
        raise Exception("Didn't succeed")
    except Exception as e:
      eprint("error",r,r.text,e)
  if r.status_code == 404:
    eprint(f"{namespace}:{name} not found")
  if r.status_code == 403:
    eprint(f"Access denied")
    exit(1)