import React, { Component } from "react"; import { Form, Button, Alert } from "bootstrap-4-react"; import "./paste.css"; import "highlight.js/styles/github.css"; import Highlight from "react-highlight"; import Markdown from 'react-markdown'; import pako from "pako"; import seedrandom from "seedrandom"; import { remoteService } from "./RemoteService" import RIPEMD160 from "crypto-js/ripemd160"; import SHA256 from "crypto-js/sha256"; import AES from "crypto-js/aes"; import BASE64 from "crypto-js/enc-base64"; import HEX from "crypto-js/enc-hex"; import UTF8 from "crypto-js/enc-utf8" const rng = seedrandom(); const req = remoteService(); const b64 = (s) => (!!s?BASE64.stringify(s).replace(/[=]+/, '').replace(/\//g, '_').replace(/\+/g, '-'):''); const sha = (s) => b64(SHA256(s)); const chk = (s) => b64(RIPEMD160(SHA256(s))); const enc = (t, p) => AES.encrypt(t, p).toString(); const u16a = (ua) => { let s = ''; for (let i = 0; i < ua.length; i++) { s += ('0' + ua[i].toString(16)).slice(-2); } return HEX.parse(s); }; const u8a = (wa) => { const w = wa.words; let b = new Uint8Array(w.length * 4), v, i, j, k = 0; for (i = 0; i < w.length; ++i) { v = w[i]; for (j = 3; j >= 0; --j) { b[k++] = ((v >> 8 * j) & 0xFF); } } return b; }; const str8 = (ua) => { let s = ''; for (let i = 0; i < ua.byteLength; i++) { if ((ua[i]&0x80) === 0) s += String.fromCharCode(ua[i]); else if ((ua[i]&0xe0) === 0xc0 && (ua[i+1]&0xc0) === 0x80) { s += String.fromCharCode(((ua[i]&0x1f)<<6) + (ua[i+1]&0x3f)); i += 1; } else if ((ua[i]&0xf0) === 0xe0 && (ua[i+1]&0xc0) === 0x80 && (ua[i+2]&0xc0) === 0x80){ s += String.fromCharCode(((ua[i]&0x0f)<<12) + ((ua[i+1]&0x3f)<<6) + (ua[i+2]&0x3f)); i += 2; } else if ((ua[i]&0xf8) === 0xf0 && (ua[i+1]&0xc0) === 0x80 && (ua[i+2]&0xc0) === 0x80 && (ua[i+3]&0xc0) === 0x80) { s += String.fromCharCode(((ua[i]&0x0f)<<18) + ((ua[i+1]&0x3f)<<12) + ((ua[i+2]&0x3f)<<6) + (ua[i+3]&0x3f)); i += 3; } else { s += String.fromCharCode(65533); } } return s; }; const zip = (text) => u16a(pako.gzip(text)); const dec = (c, p, z) => { if (z) { let tx = AES.decrypt(c, p); tx = u8a(tx); return str8(pako.inflate(tx)); } else { return AES.decrypt(c, p).toString(UTF8); } } const blength = (o) => { if (!(typeof o === 'string' || o instanceof String)) return 0; if (o === undefined || o.length === undefined) return 0; var utf8length = 0; for (var n = 0; n < o.length; n++) { var c = o.charCodeAt(n); if (c < 128) { utf8length++; } else if ((c > 127) && (c < 2048)) { utf8length = utf8length + 2; } else { utf8length = utf8length + 3; } } return utf8length; }; const PASTE_API = (''+window.location).replace(/ui\/.*$/, 'paste'); const syntaxItems = [ ["text", "Plain Text"], ["markdown", "Markdown"], ["apache", "Apache"], ["bash", "Bash"], ["coffeescript", "CoffeeScript"], ["cpp", "C++"], ["cs", "C#"], ["css", "CSS"], ["diff", "Diff"], ["http", "HTTP"], ["ini", "Ini"], ["java", "Java"], ["javascript", "JavaScript"], ["json", "JSON"], ["makefile", "Makefile"], ["nginx", "Nginx"], ["objectivec", "Objective C"], ["perl", "Perl"], ["php", "PHP"], ["python", "Python"], ["ruby", "Ruby"], ["sql", "SQL"], ["xml", "HTML, XML"] ]; const expireItems = [ [3600, "1 Hour"], [86400, "1 Day"], [604800, "1 Week"], [2419200, "4 Weeks"], [15778463, "6 Months"], [31556926, "1 Year"] ]; class Paste extends Component { constructor(props) { super(props); const [ hash, key ] = window.location.hash.substring(2).split("!"); this.state = { error: "", syntax: "text", expire: 604800, expires: "", burn: false, plain: "", cipher: "", decryptKey: hash !== "new" ? key : "", hash: hash !== "new" ? hash : "", entropy: 0, gzip: false, syntaxItems, expireItems }; this.startEntropy = this.startEntropy.bind(this); this.addEntropy = this.addEntropy.bind(this); this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.onNew = this.onNew.bind(this); this.onCopy = this.onCopy.bind(this); this.encrypt = this.encrypt.bind(this); this.decrypt = this.decrypt.bind(this); } startEntropy(events, count) { let t = []; let fn = (e) => { t.push([e.pageX, e.pageY, e.keyCode, +new Date()]); if (t.length < count) { return; } this.addEntropy(t); t = []; }; fn = fn.bind(this); for (let i in events) { if (events.hasOwnProperty(i)) document.addEventListener(events[i], fn); } }; addEntropy(s) { this.setState(function(state, props) { return {entropy: state.entropy + s.length}; } ); seedrandom(s, {entropy: true}); }; onChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; this.setState({ [name]: value }); } onSubmit(event) { event.preventDefault(); const { plain } = this.state; // const { history } = this.props; this.encrypt(plain).then(([hash, decryptKey]) => { history.pushState({}, '', '/ui/#/' + hash + '!' + decryptKey) }); } onNew(event) { // const { history } = this.props; this.setState({cipher:"", plain:"", hash: "", decryptKey: "", syntax: "text", expire: 604800, burn: false}, () => history.pushState({}, '', '/ui')); } onCopy(event) { // const { history } = this.props; this.setState({hash: "", decryptKey: ""}, () => history.pushState({}, '', '/ui')) } decrypt(tx) { if (tx === "") { this.setState({hash: "", decryptKey: "", error: "Unable to retrieve paste."}) return; } let s = tx.split('\n'); let i = 0; let header = {}; while (true) { if (s[i] === "") break; var l = s[i].trim().split(':\t'); header[l[0]] = l[1]; i++; } const cipher = s.splice(i).join(''); const zip = !!header.zip && header.zip === "true"; const { decryptKey } = this.state; const plain = dec(cipher, decryptKey, zip); const expires = !!header.exp ? (!!header.burn ? "Burn on Read" : ((d) => d.toLocaleDateString() + " " + d.toLocaleTimeString())(new Date(header.exp*1000))) : "Never"; this.setState({cipher: tx, plain: plain, expires, syntax: header.lang}); } encrypt(input) { const rnd = rng(40); const decryptKey = sha(rnd); const gzip = blength(input)>4000; const plain = gzip?zip(input):input; const { syntax, expire, burn } = this.state; const header = { chk: chk(rnd), lang: syntax, exp: parseInt(expire, 10) + (Date.now() / 1000 | 0), zip: gzip, burn: burn } let s = '', e = enc(plain, decryptKey); while (e.length > 79) { s += e.slice(0, 79) + "\n"; e = e.slice(79); } s += e + "\n"; const expires = !!header.exp ? (!!header.burn ? "Burn on Read" : ((d) => d.toLocaleDateString() + " " + d.toLocaleTimeString())(new Date(header.exp*1000))) : "Never"; const cipher = Object.entries(header).map(([k, v]) => !!v ? k + ":\t" + v + "\n" : "").join('') + "\n" + s; return req(PASTE_API).post({}, cipher) .then((r) => r.text()) .then((d) => { console.log("Received:\n" + d); const [ok='', hash=''] = d.split(' ', 2); if (ok === "OK") this.setState((state) => ({cipher, gzip, decryptKey, hash, expires})); else console.log(d); return [hash, decryptKey]; }); } componentDidMount() { this.startEntropy(['mousemove', 'keydown', 'keypress', 'click', 'scroll'], 16); req(`${PASTE_API}/rng`).get().then((res)=>res.text()).then(this.addEntropy).catch(); const { hash } = this.state; if (hash !== "") req(`${PASTE_API}/${hash}`).get().then((res)=> res.ok ? res.text() : "").then(this.decrypt).catch(); } componentWillReceiveProps(nextProps) { const { hash:nextHash } = window.location; const [ hash='', key='' ] = nextHash.substring(2).split("!"); if (hash === this.state.hash) return; if (hash === '') this.setState({cipher:"", plain:"", hash: "", decryptKey: "", syntax: "text", expire: 604800, burn: false}) else { this.setState({hash: hash, decryptKey: key}); req(`${PASTE_API}/${hash}`).get().then((res)=> res.ok ? res.text() : "").then(this.decrypt).catch(); } } render() { const { hash } = this.state; return hash === '' ? : ; } } function PasteCreate({error, onSubmit, onChange, syntax, syntaxItems, expire, expireItems, burn, entropy, plain}) { return (
{!!error && Holy guacamole! {error} }
Additional Entropy: {entropy} bytes / Content size: {blength(plain)} bytes

Create pastes from the command line! paste.sh

{`$ echo /etc/passwd | ./paste.sh

env options:
PASTE_URL  - Set the url base for paste operations (default: HTTPS://paste.dn42.us)
PASTE_GZIP - 0 = No Compression,  1 = Use gzip compression (default: 0)
PASTE_BURN - 0 = No Burn on Read, 1 = Burn on read         (default: 0)
PASTE_DATE - Value to be used when setting expire date.    (default: next-week)`}
            
); } function PasteView({hash, decryptKey, expires, burn, gzip, cipher, plain, syntax, onNew, onCopy}) { const gzipOpts = gzip ? '| gzip -dc' : ''; return (
e.target.select()}/>

Lang: {syntax}   Expires: {expires}   {burn && ( BURN ON READ )}
{syntax==="markdown" ? ( ) : ( {plain} )}
{`# Command Line:
  curl -s "${PASTE_API}/${hash}" \\
    | sed "1,/^\\$/d" \\
    | openssl aes-256-cbc -md md5 \\
              -d -a -k "${decryptKey}" ${gzipOpts}

${cipher}`}
); } export default Paste;