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)