@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
556 lines (549 loc) • 16.4 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint node:true*/
const zconf = require('./config.js');
const array = require('./array.js');
const crypto = require('crypto');
const rimraf = require('rimraf');
const path = require('path');
const fs = require('fs');
const StringDecoder = require('string_decoder').StringDecoder;
const E = exports, assign = Object.assign;
// file.xxx_e() throw exceptions. file.xxx() return null/false on fail.
E.errno = 0; // an integer/string error code
// XXX sergey: please implement
E.error = null; // an Error() object
E.read_buf_size = 8192;
E.is_win = /^win/.test(process.platform);
E.is_darwin = /^darwin/.test(process.platform);
E.is_linux = /^linux/.test(process.platform);
E.is_zlinux = E.is_linux && !/ARCH=ANDROID/.test(zconf.CONFIG_MAKEFLAGS);
E.is_android = E.is_linux && !E.is_zlinux;
let check_file = (dst, opt)=>{
opt = opt||{};
if (opt.rm_rf)
E.rm_rf(dst);
if (opt.mkdirp)
E.mkdirp_file_e(dst);
if (opt.unlink)
E.unlink(dst);
};
let FileError = (msg, code)=>{
let err = new Error(code+' '+msg);
err.code = code;
return err;
};
E.read_e = (filename, opt)=>{
if (opt===undefined)
opt = 'utf8';
return fs.readFileSync(filename, opt);
};
E.fread_cb_e = (fd, pos, cb)=>{
let res, buf = Buffer.alloc(E.read_buf_size);
while (res = fs.readSync(fd, buf, 0, buf.length, pos))
{
if (cb(buf.slice(0, res), pos))
return true;
pos += res;
}
return true;
};
E.read_cb_e = (filename, pos, cb)=>{
let fd = fs.openSync(filename, 'r');
try { return E.fread_cb_e(fd, pos, cb); }
finally { fs.closeSync(fd); }
};
E.fread_line_cb_e = (fd, cb, opt)=>{
opt = assign({encoding: 'utf8', buf_size: E.read_buf_size}, opt);
cb = cb||(()=>false);
let read, buf = Buffer.alloc(opt.buf_size);
let strbuf = '', lf_idx, decoder = new StringDecoder(opt.encoding);
while (read = fs.readSync(fd, buf, 0, buf.length))
{
strbuf += decoder.write(buf.slice(0, read));
while ((lf_idx = strbuf.indexOf('\n'))>=0)
{
if (cb(strbuf.slice(0, lf_idx-(strbuf[lf_idx-1]=='\r' ? 1 : 0))))
return true;
strbuf = strbuf.slice(lf_idx+1);
}
}
if (strbuf)
cb(strbuf);
return true;
};
E.read_line_cb_e = (filename, cb, opt)=>{
let fd = fs.openSync(filename, 'r');
try { return E.fread_line_cb_e(fd, cb, opt); }
finally { fs.closeSync(fd); }
};
E.read_line_e = filename=>{
let ret;
E.read_line_cb_e(filename, line=>(ret = line, true));
return ret;
};
E.read_lines_e = filename=>{
let ret = E.read_e(filename).split(/\r?\n/);
if (ret[ret.length-1]=='')
ret.pop();
return ret;
};
E.fread_e = (fd, start, size, opt)=>{
opt = opt||{};
let decoder = new StringDecoder(opt.encoding), rest = size;
let ret = '', append_ret = buf=>ret += decoder.write(buf);
E.fread_cb_e(fd, start, rest===undefined ? append_ret : buf=>{
rest -= buf.length;
if (rest<0)
buf = buf.slice(0, rest);
append_ret(buf);
return rest<=0;
});
return ret;
};
E.write_e = (file, data, opt)=>{
opt = opt||{};
check_file(file, opt);
fs.writeFileSync(file, data, opt);
return true;
};
E.write_atomic_e = (file, data, opt)=>{
opt = opt||{};
check_file(file, opt);
let tmpfile = file+'.'+(1000000*Math.random()|0)+'.tmp';
try {
fs.writeFileSync(tmpfile, data, opt);
fs.renameSync(tmpfile, file);
} catch(e){
E.unlink(tmpfile);
throw e;
}
return true;
};
E.write_lines_e = (file, data, opt)=>{
data = Array.isArray(data) ?
data.length ? data.join('\n')+'\n' : '' : ''+data+'\n';
return E.write_e(file, data, opt);
};
E.append_e = (file, data, opt)=>{
opt = opt||{};
check_file(file, opt);
fs.appendFileSync(file, data, opt);
return true;
};
E.head_e = (file, size)=>{
if (size<0)
size = 0;
let fd = fs.openSync(file, 'r');
try { return E.fread_e(fd, 0, size); }
finally { fs.closeSync(fd); }
};
E.tail_e = (file, count)=>{
let fd, start;
count = count||E.read_buf_size;
start = E.size_e(file)-count;
if (start<0)
start = 0;
fd = fs.openSync(file, 'r');
try { return E.fread_e(fd, start); }
finally { fs.closeSync(fd); }
};
E.size_e = file=>fs.statSync(file).size;
E.mtime_e = file=>+fs.statSync(file).mtime;
function mkdirp(p, mode){
if (typeof mode=='string')
mode = parseInt(mode, 8);
let made = null;
p = path.resolve(p);
let paths = [];
while (p && !E.exists(p))
{
paths.unshift(p);
p = path.dirname(p);
}
for (let i=0; i<paths.length; i++)
{
fs.mkdirSync(paths[i], mode);
made = made||paths[i];
}
return made||p;
}
E.mkdirp_e = (p, mode)=>{
if (mode===undefined || !process.umask)
return mkdirp(p);
let oldmask = process.umask(0);
try { return mkdirp(p, mode); }
finally { process.umask(oldmask); }
};
E.mkdirp_file_e = (file, mode)=>{
E.mkdirp_e(path.dirname(file), mode);
return file;
};
E.rm_rf_e = rimraf.sync;
E.unlink_e = src=>{
fs.unlinkSync(src);
return true;
};
E.rmdir_e = dir=>{
fs.rmdirSync(dir);
return true;
};
E.touch_e = (src, d)=>{
let tm = (d||Date.now())/1000;
let h = fs.openSync(src, 'a');
fs.futimesSync(h, tm, tm);
fs.closeSync(h);
return true;
};
E.readdir_e = (dir, opt)=>{
let names = fs.readdirSync(dir);
if (!opt || !!opt.dirs==!!opt.files)
return names;
if (opt.dirs)
return names.filter(n=>E.is_dir(dir+'/'+n));
return names.filter(n=>E.is_file(dir+'/'+n));
};
E.readdir_r_e = (dir, opt)=>{
dir = E.normalize(dir).replace(/\/+$/, '');
return E.find_e(dir, assign({strip: new RegExp('^'+dir+'/')}, opt));
};
function get_owner(stat, opt){
let has_uid = 'user' in opt, has_gid = 'group' in opt;
if (!has_uid&&!has_gid&&!opt.preserve)
return;
return {
user: has_uid ? opt.user :
opt.preserve||E.is_win ? stat.uid : process.getuid(),
group: has_gid ? opt.group :
opt.preserve||E.is_win ? stat.gid : process.getgid(),
};
}
function copy_file(src, dst, opt){
let fdw, stat, mode;
opt = opt||{};
stat = fs.statSync(src);
if (E.is_dir(dst)||dst[dst.length-1]=='/')
dst = dst+'/'+path.basename(src);
if (opt.no_overwrite && E.exists(dst))
{
if (opt.no_overwrite=='skip')
{
if (opt.verbose)
console.log(`Skipping copy (already exists): ${src}->${dst}`);
return true;
}
throw FileError(`file already exists: ${dst}`, 'EEXIST');
}
check_file(dst, opt);
mode = 'mode' in opt ? opt.mode : stat.mode & 0o777;
fdw = fs.openSync(dst, 'w', mode);
try {
E.read_cb_e(src, 0, buf=>void fs.writeSync(fdw, buf, 0, buf.length));
let owner = get_owner(stat, opt);
if (owner)
fs.fchownSync(fdw, owner.user, owner.group);
// Does it really needed?
if (opt.preserve_ts)
fs.futimesSync(fdw, stat.atime, stat.mtime);
} finally { fs.closeSync(fdw); }
if (opt.verbose)
console.log(`Copy: ${src}->${dst}`);
return true;
}
E.copy_e = (src, dst, opt)=>{
src = E.normalize(src);
dst = E.normalize(dst);
if (E.is_dir(src))
throw FileError('cannot copy directory, use copy_r', 'EISDIR');
return copy_file(src, dst, opt);
};
E.copy_r_e = (src, dst, opt)=>{
opt = assign({mkdirp: true}, opt);
src = E.normalize(src);
dst = E.normalize(dst);
if (opt.exclude && opt.exclude.test(src))
return true;
if (E.is_file(src))
return copy_file(src, dst, opt);
if (E.is_win && E.is_symlink(src))
src = E.readlink_e(src);
return E.readdir_r_e(src, opt).every(f=>
E.copy_r_e(src+'/'+f, dst+'/'+f, opt));
};
E.rename_e = (src, dst)=>{
fs.renameSync(src, dst);
return true;
};
E.readlink_e = src=>fs.readlinkSync(src);
E.link_e = (src, dst, opt)=>{
opt = opt||{};
src = E.normalize(src);
dst = E.normalize(dst);
check_file(dst, opt);
try { fs.linkSync(src, dst); }
catch(e){
if (opt.no_copy)
throw e;
return E.copy_r_e(src, dst, opt);
}
return true;
};
E.link_r_e = (src, dst, opt)=>{
opt = assign({mkdirp: true}, opt);
src = E.normalize(src);
dst = E.normalize(dst);
if (opt.exclude && opt.exclude.test(src))
return true;
if (!opt.follow_symlinks && E.is_symlink(src))
return E.symlink_e(src, dst, opt);
if (!E.is_dir(src))
return E.link_e(src, dst, opt);
return E.readdir_r_e(src, opt).every(f=>
E.link_e(src+'/'+f, dst+'/'+f, opt));
};
E.symlink_e = (src, dst, opt)=>{
opt = opt||{};
if (E.is_win && !opt.force)
return E.link_e(src, dst, opt);
src = E.normalize(src);
dst = E.normalize(dst);
check_file(dst, opt);
let target = src;
if (!opt.keep_relative)
target = fs.realpathSync(src);
else if (E.is_symlink(src))
target = E.readlink_e(src);
fs.symlinkSync(target, dst);
return true;
};
E.hashsum_e = (filename, type)=>{
let hash = crypto.createHash(type||'md5');
E.read_cb_e(filename, 0, buf=>void hash.update(buf));
return hash.digest('hex');
};
let hash_re = /([0-9a-fA-F]+) [ |*](.*)/;
E.hashsum_check_e = (type, filename)=>{
let data = E.read_lines_e(filename);
let base = path.dirname(filename);
for (let i=0; i<data.length; i++)
{
let match = hash_re.exec(data[i]);
if (!match)
throw new Error('Incorrect line found: '+data[i]);
let source = E.absolutize(match[2], base);
let hash = E.hashsum_e(source, type);
if (hash!=match[1])
throw new Error('Hash mismatch '+source+': '+hash+' != '+match[1]);
}
return true;
};
// Safe methods
function errno_wrapper(func, ret){
let args = array.slice(arguments, 2);
E.errno = 0;
E.error = null;
try { return func.apply(null, args); }
catch(e){
E.errno = e.code||e;
E.error = e;
return ret;
}
}
function find_cb(dir, cb, opt){
opt = opt||{};
let exclude = opt.exclude, match = opt.match, strip = opt.strip;
function proc(f){
if (match && !match.test(f))
return;
cb(f);
}
E.readdir_e(dir).forEach(f=>{
let name = E.normalize(dir+'/'+f);
let stripped = strip ? name.replace(strip, '') : name;
if (exclude && exclude.test(stripped))
return;
if (E.is_dir(name))
{
if (opt.dirs)
proc(stripped);
if (!opt.follow_symlinks && E.is_symlink(name))
return;
find_cb(name, cb, opt);
}
else
proc(stripped);
});
}
E.find_e = (dir, opt)=>{
dir = E.normalize(dir);
opt = opt||{};
if (opt.cb)
return find_cb(dir, opt.cb, opt);
let ret = [];
find_cb(dir, f=>ret.push(f), opt);
return ret;
};
E.realpath_e = src=>fs.realpathSync(src);
E.stat_e = src=>fs.statSync(src);
E.lstat_e = src=>fs.lstatSync(src);
let err_retval = {
read: null, read_line: null, read_lines: null, fread: null,
tail: null, head: null, size: null, mkdirp: null, mkdirp_file: null,
hashsum: null, stat: null, lstat: null, realpath: null, readlink: null,
hashsum_check: false, fread_cb: false, read_cb: false,
fread_line_cb: false, read_line_cb: false, write: false,
write_atomic: false, write_lines: false,
append: false, unlink: false, rmdir: false, rm_rf: false, touch: false,
readdir: [], readdir_r: [], copy: false, copy_r: false, rename: false,
link: false, link_r: false, symlink: false, mtime: -1, find: null,
};
for (let method in err_retval)
E[method] = errno_wrapper.bind(null, E[method+'_e'], err_retval[method]);
E.exists = src=>{
try { fs.accessSync(src); }
catch(e){ return false; }
return true;
};
E.is_file = src=>{
let stat;
try { stat = fs.statSync(src); }
catch(e){ return false; }
return stat.isFile();
};
E.is_dir = src=>{
let stat;
try { stat = fs.statSync(src); }
catch(e){ return false; }
return stat.isDirectory();
};
E.is_symlink = src=>{
let stat;
try { stat = fs.lstatSync(src); }
catch(e){ return false; }
return stat.isSymbolicLink();
};
E.is_chardev = src=>{
let stat;
try { stat = fs.statSync(src); }
catch(e){ return false; }
return stat.isCharacterDevice();
};
E.is_socket = src=>{
let stat;
try { stat = fs.statSync(src); }
catch(e){ return false; }
return stat.isSocket();
};
E.is_exec = src=>{
try { fs.accessSync(src, fs.X_OK); }
catch(e){ return false; }
return true;
};
E.which = (bin, env)=>{
bin = E.normalize(bin);
if (E.is_absolute(bin)&&E.is_exec(bin))
return bin;
const pathvar = env&&env.PATH||process.env.PATH;
let paths = pathvar.split(E.is_win ? ';' : ':');
for (let i=0; i<paths.length; i++)
{
let filename = E.normalize(`${paths[i]}/${bin}`);
// In cygwin .exe extensions is omitting
if (E.is_win&&!E.exists(filename)&&E.exists(filename+'.exe'))
filename += '.exe';
if (E.exists(filename)&&E.is_exec(filename))
return filename;
}
};
let watch_files = {};
E.file_changed = (file_path, scope)=>{
scope = scope||'';
let mtime = E.mtime(file_path);
watch_files[scope] = watch_files[scope]||{};
if (watch_files[scope][file_path]===mtime)
return false;
watch_files[scope][file_path] = mtime;
return true;
};
if (E.is_win)
{
E.cygwin_root = E.is_dir('C:/cygwin') ? 'C:/cygwin' :
E.is_dir('D:/cygwin') ? 'D:/cygwin' : null;
}
E.cyg2unix = src=>{
if (!E.is_win || !E.cygwin_root)
return src;
// /cygdrive/X/yyy --> X:/yyy
src = src.replace(/^\/cygdrive\/(.)(\/(.*))?$/, '$1:/$3');
// /usr/lib --> c:/cygwin/lib
src = src.replace(/^\/usr\/lib(\/.*)?$/, E.cygwin_root.toLowerCase()+
'/lib$1');
// /usr/bin --> c:/cygwin/bin
src = src.replace(/^\/usr\/bin(\/.*)?$/, E.cygwin_root.toLowerCase()+
'/bin$1');
// /xxx --> c:/cygwin/xxx
src = src.replace(/^\//, E.cygwin_root.toLowerCase()+'/');
return src;
};
E.unix2cyg = src=>{
if (!E.is_win)
return src;
// c:/cygwin/lib -> /usr/lib
src = src.replace(new RegExp('^'+E.cygwin_root.toLowerCase()+'/lib(.*)'),
'/usr/lib$1');
// c:/cygwin/bin -> /usr/bin
src = src.replace(new RegExp('^'+E.cygwin_root.toLowerCase()+'/bin(.*)'),
'/usr/bin$1');
// c:/cygwin/xxx -> /xxx
src = src.replace(new RegExp('^'+E.cygwin_root.toLowerCase()+'/(.*)'),
'/$1');
return src;
};
E.unix2win = src=>{
if (!E.is_win)
return src;
// c:/xxx -> C:/xxx
src = src.replace(/^[c-z]:/, s=>s.toUpperCase());
// C:/xxx/yyy -> C:\xxx\yyy
src = src.replace(/\//g, '\\');
return src;
};
E.win2unix = (src, force)=>{
if (!force && !E.is_win)
return src;
// C:\xxx\yyy --> C:/xxx/yyy
src = src.replace(/\\/g, '/');
// C:/ --> c:/
src = src.replace(/^[c-z]:/i, s=>s.toLowerCase());
return src;
};
E.win2cyg = src=>{
if (!E.is_win)
return src;
src = E.win2unix(src);
let escaped_root = E.cygwin_root.replace(/([?\\/[\]+*])/g, '\\$1');
src = src.replace(new RegExp('^'+escaped_root+'/?', 'i'), '/');
src = src.replace(/^[c-z]:/i, s=>'/cygdrive/'+s[0].toLowerCase());
return src;
};
E.is_dotfile = src=>src.split('/').pop().startsWith('.');
E.is_absolute = src=>/^(\/|([c-z]:))/i.test(src);
E.absolutize = (p, d1, d2)=>{
if (!p||E.is_absolute(p))
return p;
if (d2&&E.exists(d2+'/'+p))
return d2+'/'+p;
return d1+'/'+p;
};
E.normalize = p=>E.cyg2unix(E.win2unix(path.normalize(p)));
E.is_subdir = (root, sub)=>{
let nroot = root.length;
return !root || sub.startsWith(root) && (root[nroot-1]=='/' ||
sub[nroot]===undefined || sub[nroot]=='/');
};
E.chown_e = (src, opt)=>{
let stat = fs.statSync(src);
let owner = get_owner(stat, opt);
if (owner)
return fs.chownSync(src, owner.user, owner.group);
};
E.chmod_e = (src, mode)=>fs.chmodSync(src, mode);