tags: php python clipboard net-clipboard title: Simple Web-based Clipboard in PHP 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](/lang/python/net/tcp/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 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 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 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 ```py #!/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]} [...]") 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 ```py #!/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]} [...]") 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 ```py #!/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]} [...]") 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) ```