From 537486da6bf93c66180714102174f14706cab1f6 Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Sat, 29 Aug 2015 09:54:34 +0200 Subject: Initial --- .gitignore | 3 + client.js | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 26 ++++++ serverstore.js | 161 +++++++++++++++++++++++++++++++++ 4 files changed, 464 insertions(+) create mode 100644 .gitignore create mode 100755 client.js create mode 100644 package.json create mode 100755 serverstore.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ba2c80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +images +node_modules +.DS_Store diff --git a/client.js b/client.js new file mode 100755 index 0000000..00d4abb --- /dev/null +++ b/client.js @@ -0,0 +1,274 @@ +#!/usr/bin/env node + +var fs=require("fs"), + path=require("path"), + http=require("http"), + crypto=require("crypto"), + dialog=require("dialog"), + kbd=require("kbd"); + +var HOSTNAME="localhost",HTTPPORT=42420; + +var userid="0",password="-"; + + +var HOMEDIR=process.env.HOME||process.env.HOMEPATH||process.env.HOMEDIR||process.cwd(); +var WATCHDIR=HOMEDIR+"/Desktop"; +var ignored=[]; + +var currentState=[]; + +//lots of code taken/modified from tomsmeding/gvajnez +function collectDirState(dir){ + if(!dir)dir=directory; + //console.log("collectDirState("+dir+")"); + var list=fs.readdirSync(dir); + var result=[]; + var statinfo,i,j; + for(i=0;i15||(now-namedate)/1000>15)continue; //15 seconds limit + sendfile(changes[i].name); + } +} + +function authhash(challenge,password){ + var s=challenge+password; + var hasher=crypto.createHash("sha256"); + hasher.update(s); + return hasher.digest("hex"); +} + +function getchallenge(cb){ + console.log("Going to request challenge..."); + var req=http.request({ + hostname:HOSTNAME, + port:HTTPPORT, + path:"/challenge", + method:"GET", + keepAlive:true //speed up the next request + },function(res){ + var body=""; + res.on("data",function(data){ + body+=data; + }); + res.on("end",function(){ + if(res.statusCode!=200){ + dialog.warn("Could not request challenge! Is your internet connection alive?\n\n"+body); + return; + } + console.log("challenge = "+body); + cb(body); + }); + }); + req.on("error",function(err){ + console.log(err); + }); + req.end(); +} +function sendfile(fname,retries){ + retries=retries!=null?retries:3; + var barefname; + var idx=fname.lastIndexOf("/"); + if(idx==-1)barefname=fname; + else barefname=fname.slice(idx+1); + getchallenge(function(challenge){ + var req=http.request({ + hostname:HOSTNAME, + port:HTTPPORT, + path:"/image/"+userid+"/"+authhash(challenge,password)+"/"+escape(barefname), + method:"POST", + headers:{ + "Content-Type":"image/png" + } + },function(res){ + var body=""; + res.on("data",function(data){ + body+=data; + }); + res.on("end",function(){ + if(res.statusCode!=200){ + if(retries>0)sendfileChallenge(fname,challenge,retries-1); + else { + dialog.warn("Could not upload image! Are your credentials still okay?\n\n"+body); + return; + } + } + console.log("Successful upload"); + fs.unlink(fname); //not sync, take your time + dialog.info(body); + }); + }); + req.on("error",function(err){ + console.log(err); + }); + req.end(fs.readFileSync(fname)); + }); +} + + +function userExists(userid,cb){ + var req=http.request({ + hostname:HOSTNAME, + port:HTTPPORT, + path:"/exists/"+userid, + method:"GET" + },function(res){ + if(res.statusCode==200)cb(true); + else if(res.statusCode==404)cb(false); + else { + console.log("Server returned status code "+res.statusCode+" for exists query!"); + } + }); + req.on("error",function(err){ + console.log(err); + }); + req.end(); +} + +function checkLogin(userid,password,cb){ + getchallenge(function(challenge){ + var req=http.request({ + hostname:HOSTNAME, + port:HTTPPORT, + path:"/checklogin/"+userid+"/"+authhash(challenge,password), + method:"GET" + },function(res){ + if(res.statusCode==200)cb(true); + else if(res.statusCode==404||res.statusCode==403)cb(false); + else { + console.log("Server returned status code "+res.statusCode+" for checklogin query!"); + } + }); + req.on("error",function(err){ + console.log(err); + }); + req.end(); + }); +} + +function registerUser(userid,password){ + var req=http.request({ + hostname:HOSTNAME, + port:HTTPPORT, + path:"/registerx/"+userid, + method:"POST", + headers:{ + "Content-Type":"text/plain" + } + },function(res){ + var body=""; + res.on("data",function(data){ + body+=data; + }); + res.on("end",function(){ + if(res.statusCode==200)console.log("Successfully registered user "+userid); + else if(res.statusCode==409)console.log("Conflict: "+body); + else console.log("Error: "+body); + }); + }); + req.on("error",function(err){ + console.log(err); + }); + req.end(password); +} + + + +process.stdout.write("Username? "); +userid=kbd.getLineSync().replace(/[^a-zA-Z0-9_-]/g,""); +process.stdout.write("Password? "); +kbd.setEcho(false); +password=kbd.getLineSync(); +kbd.setEcho(true); +console.log("\nChecking existence..."); + +userExists(userid,function(exists){ + if(exists){ + checkLogin(userid,password,function(ok){ + if(ok)console.log("User login ok."); + else { + console.log("Username or password incorrect!"); + process.exit(); + } + }); + return; + } + process.stdout.write("That username doesn't seem to exist. Register it? [y/N] "); + var response=kbd.getLineSync()[0]; + if(response=="y"||response=="Y")registerUser(userid,password); + else { + console.log("Not registered. Exiting."); + process.exit(); + } +}); + +var timeout=null; +var watcher=fs.watch(WATCHDIR,{persistent:true,recursive:false},function(ev,fname){ + //console.log("change in directory "+WATCHDIR+" (fname "+fname+")"); + if(timeout)return; + timeout=setTimeout(function(){ + var newstate=collectDirState(WATCHDIR); + var changes=collectChanges(WATCHDIR,newstate).map(function(o){o.name=o.name.replace(/^\.\//,"");return o;}); + currentState=newstate; + if(changes.length!=0)handleChanges(changes); + timeout=null; + //console.log(currentState); + },500); +}); +currentState=collectDirState(WATCHDIR); +console.log("-- (Client ready.)"); diff --git a/package.json b/package.json new file mode 100644 index 0000000..53a1bf3 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "serverstore", + "version": "1.0.0", + "description": "A Puush alternative", + "main": "serverstore.js", + "dependencies": { + "body-parser": "^1.13.3", + "dialog": "^0.1.8", + "express": "^4.13.3", + "kbd": "^0.0.8", + "glob": "^5.0.14", + "node-persist": "^0.0.6" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "image", + "hosting", + "server", + "screenshot" + ], + "author": "Tom Smeding (http://tomsmeding.com)", + "license": "MIT" +} diff --git a/serverstore.js b/serverstore.js new file mode 100755 index 0000000..b2d7085 --- /dev/null +++ b/serverstore.js @@ -0,0 +1,161 @@ +#!/usr/bin/env node + +var fs=require("fs"), + path=require("path"), + app=require("express")(), + bodyParser=require("body-parser"), + crypto=require("crypto"), + Persist=require("node-persist"), + glob=require("glob"); + +var HTTPPORT=42420; + +Persist.initSync({}); + +var challenge=null; + +function renewChallenge(){ + var entropy=crypto.randomBytes(256); + var hasher=crypto.createHash("sha256"); + hasher.update(entropy); + challenge=hasher.digest("hex"); +} +setInterval(renewChallenge,8000); +renewChallenge(); + +var gencode=(function(){ + const startn=42424242; + const alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var lastn=Persist.getItemSync("gencode_lastn"); + if(lastn==null)lastn=startn; + return function gencode(){ + var code="",coden=lastn; + while(coden){ + code+=alphabet[coden%alphabet.length]; + coden=~~(coden/alphabet.length); + } + if(lastn==0x7fffffff)lastn=0; //maximum value of a 32-bit int + else lastn++; + Persist.setItemSync("gencode_lastn",lastn); + if(lastn==startn)throw new Error("RUN OUT OF INDICES PANIC PANIC"); + while(code.length<6)code+="a"; + return code; + }; +})(); + + +app.use(bodyParser.raw({ + limit:"3mb", + type:"image/*" +})); + +app.use(bodyParser.text({ + type:"text/plain" +})); + + +app.get("/challenge",function(req,res){ + res.end(challenge); +}); + + +app.param("userid",function(req,res,next,userid){ + var password=Persist.getItemSync("user_"+userid); + if(!password){ + res.sendStatus(404); + res.end("Non-existent userid"); + return; + } + req.ssuser=[userid,password]; + next(); +}); +app.param("authhash",function(req,res,next,authhash){ + var s=challenge+req.ssuser[1]; + var hasher=crypto.createHash("sha256"); + hasher.update(s); + var hashres=hasher.digest("hex"); + if(hashres!=authhash){ + res.sendStatus(403); + res.end("Invalid answer to challenge"); + return; + } + next(); +}); +app.param("fname",function(req,res,next,fname){ + req.sscode=gencode(); + req.ssfname=req.sscode+"_"+fname.replace(/[\x00-\x1F\/]/g,"").replace(/^\.+/,""); + next(); +}); +app.post("/image/:userid/:authhash/:fname",function(req,res){ + try { + if(!fs.statSync("images").isDirectory())throw new Error; + } catch(e){ + fs.mkdirSync("images"); + if(!fs.statSync("images").isDirectory())throw new Error; + } + if(!fs.existsSync("images/"+req.ssuser[0])){ + fs.mkdirSync("images/"+req.ssuser[0]); + } + fs.writeFileSync("images/"+req.ssuser[0]+"/"+req.ssfname,req.body); + res.end("http://"+req.hostname+(HTTPPORT!=80?":"+HTTPPORT:"")+"/ssimg/"+req.ssuser[0]+"/"+req.sscode); +}); + + +app.param("reguserid",function(req,res,next,reguserid){ + req.ssreguserid=reguserid; + next(); +}); +app.post("/registerx/:reguserid",function(req,res){ //pass password in body + var password=req.body; + if(Persist.getItemSync("user_"+req.ssreguserid)){ + res.sendStatus(409); //Conflict + res.end("That userid already exists"); + return; + } + Persist.setItemSync("user_"+req.ssreguserid,password); + res.sendStatus(200); + res.end(); +}); + + +app.get("/exists/:reguserid",function(req,res){ + res.sendStatus(Persist.getItemSync("user_"+req.ssreguserid)?200:404); + res.end(); +}); + +app.get("/checklogin/:userid/:authhash",function(req,res){ + res.sendStatus(200); + res.end(); +}); + + +app.param("ssimgcode",function(req,res,next,ssimgcode){ + req.ssimgcode=ssimgcode.replace(/[^a-zA-Z0-9]/g,""); + if(req.ssimgcode.length!=6){ + res.sendStatus(404); + res.end("Invalid or unknown image code"); + return; + } + next(); +}); +app.get("/ssimg/:userid/:ssimgcode",function(req,res){ + var files=glob.sync(__dirname+"/images/"+req.ssuser[0]+"/"+req.ssimgcode+"*"); + if(files.length==0){ + res.sendStatus(404); + res.end("Unknown image code"); + return; + } + if(files.length>1){ + console.log("More than one file matched; internal error"); + console.log(files); + res.sendStatus(500); + res.end("More than one file matched; internal error"); + return; + } + res.sendFile(files[0]); +}); + + +app.listen(42420,function(){ + console.log("Server started."); +}); -- cgit v1.2.3