diff options
Diffstat (limited to 'client/client.js')
-rwxr-xr-x | client/client.js | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/client/client.js b/client/client.js new file mode 100755 index 0000000..efd721c --- /dev/null +++ b/client/client.js @@ -0,0 +1,363 @@ +#!/usr/bin/env node + +var fs=require("fs"), + path=require("path"), + https=require("https"), + crypto=require("crypto"), + read=require("read"), + toClipboard=require("to-clipboard"), + notifier=require("node-notifier"); + +var HOSTNAME=process.argv[2]||"tomsmeding.com",HTTPSPORT=443; +console.log("Using server "+HOSTNAME); + + +var lockfiledesc; +var lockfilename="/tmp/.lock_serverstore_client_"+HOSTNAME; +try {lockfiledesc=fs.openSync(lockfilename,"wx");} +catch(e){ + var lockedpid=+fs.readFileSync(lockfilename); + var canoverwrite=false; + if(isNaN(lockedpid))canoverwrite=true; + try {process.kill(lockedpid,0);} + catch(e){canoverwrite=true;} + if(!canoverwrite){ + console.log("Another copy of the serverstore client with the same hostname seems to be running."); + console.log("If this is not the case, or intentional, delete the file "+lockfilename+" and try again."); + process.exit(1); + } + lockfiledesc=fs.openSync(lockfilename,"w"); +} +fs.writeSync(lockfiledesc,process.pid+"\n"); +process.on("exit",function(){ + fs.closeSync(lockfiledesc); + fs.unlinkSync(lockfilename); +}); + + +var userid="0",password="-"; + + +var HOMEDIR=process.env.HOME||process.env.HOMEPATH||process.env.HOMEDIR||process.cwd(); +var WATCHDIR; +if(process.platform=="darwin"){ + WATCHDIR=HOMEDIR+"/Desktop"; +} else { + WATCHDIR=HOMEDIR+"/screenshots"; +} +var ignored=[]; + +var currentState=[]; + +function notify(message) { + notifier.notify({ + title: "serverstore", + message: message, + }); +} + +//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;i<list.length;i++){ + for(j=0;j<ignored.length;j++){ + if(etc.endsWith(path.normalize(dir+"/"+list[i]),"/"+ignored[j]))break; + } + if(j<ignored.length)continue; + statinfo=fs.statSync(dir+"/"+list[i]); + /*if(statinfo.isDirectory()){ + result=result.concat(collectDirState(dir+"/"+list[i])); + } else if(statinfo.isFile()){*/ + result.push({ + name:path.resolve(dir+"/"+list[i]), + mode:statinfo.mode, + mtime:statinfo.mtime.getTime() + }); + //} + } + return result; +} + +function collectChanges(dir,state){ + if(!dir)dir=directory; + var i,j,obj; + var changes=[]; + for(i=0;i<state.length;i++){ + for(j=0;j<currentState.length;j++){ + if(state[i].name==currentState[j].name)break; + } + if(j==currentState.length|| + currentState[j].mode!=state[i].mode|| + currentState[j].mtime!=state[i].mtime){ + //either file didn't exist yet, or metadata has changed + changes.push(state[i]); + } + } + for(i=0;i<currentState.length;i++){ + for(j=0;j<state.length;j++){ + if(currentState[i].name==state[j].name)break; + } + if(j==state.length){ + //file doesn't exist anymore + changes.push(currentState[i]); + } + } + return changes; +} + +function handleChanges(changes){ + var i,match,namedate,now; + for(i=0;i<changes.length;i++){ + if(!fs.existsSync(changes[i].name))continue; //probably delete event + match=changes[i].name.match(/\/Screen Shot (\d{4}-\d{2}-\d{2}) at (\d{2}\.\d{2}\.\d{2}).png$/); + if(!match)continue; + namedate=new Date(match[1]+" "+match[2].replace(/\./g,":")); + if(!namedate)continue; + changes[i].mtime=new Date(changes[i].mtime); + now=new Date(); + if((now-changes[i].mtime)/1000>60||(now-namedate)/1000>60)continue; //1 minute limit + setTimeout(function(n){ + return function(){sendfile(n);}; + }(changes[i].name),100); + } +} + +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=https.request({ + hostname:HOSTNAME, + port:HTTPSPORT, + path:"/ss/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){ + notify("Could not request challenge! Is your internet connection alive?\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=https.request({ + hostname:HOSTNAME, + port:HTTPSPORT, + path:"/ss/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)sendfile(fname,retries-1); + else notify("Could not upload image! Are your credentials still okay?\n"+body); + return; + } + console.log("Successful upload"); + toClipboard.sync(body.trim()); + notify("Uploaded, link copied"); + try { + fs.unlinkSync(fname); + } catch(e){ + console.log(e); + } + }); + }); + req.on("error",function(err){ + console.log(err); + }); + try{req.end(fs.readFileSync(fname));} + catch(e){console.log(e);} + }); +} + + +function userExists(userid,cb){ + var req=https.request({ + hostname:HOSTNAME, + port:HTTPSPORT, + path:"/ss/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,retries){ + retries=retries!=null?retries:3; + getchallenge(function(challenge){ + var req=https.request({ + hostname:HOSTNAME, + port:HTTPSPORT, + path:"/ss/checklogin/"+userid+"/"+authhash(challenge,password), + method:"GET" + },function(res){ + if(res.statusCode==200)cb(true); + else if(res.statusCode==404||res.statusCode==403){ + if(retries>0)checkLogin(userid,password,cb,retries-1); + else 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=https.request({ + hostname:HOSTNAME, + port:HTTPSPORT, + path:"/ss/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); +} + +var stdinFD = null; +if (!process.stdin.isTTY) { + stdinFD = fs.openSync("/dev/stdin", "r"); +} + +function readStdinLine(options, callback) { + console.log("Entering read with " + JSON.stringify(options)); + if (stdinFD) { + var input = "", buf = Buffer.alloc(1), ch; + while (true) { + if (fs.readSync(stdinFD, buf, 0, 1, null) < 1) { + console.log("Error reading from stdin"); + process.exit(1); + } + ch = buf.toString() + if (ch == "\n") break; + input += ch; + } + callback(input); + } else { + read(options, function(err, res) { + console.log("Returned from read with " + JSON.stringify(options)); + if (err) { + console.log("Error reading from stdin: " + err); + process.exit(1); + } + callback(res); + }); + } +} + +function readUserPass(callback) { + readStdinLine({prompt: "Username? "}, function(res) { + userid = res; + + readStdinLine({prompt: "Password? ", silent: true}, function(passinput) { + var hasher = crypto.createHash("sha256"); + hasher.update(passinput); + password = hasher.digest("hex"); + + callback(); + }); + }); +} + + + +readUserPass(function() { + 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; + } + readStdinLine({prompt: "That username doesn't seem to exist. Register it? [y/N] "}, function(response) { + response = response[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.)"); +}); |