242 lines
6.2 KiB
JavaScript
242 lines
6.2 KiB
JavaScript
/** Run page JS on Bare (ctx.bare.bareVm / bare-realm) or a strict Function sandbox. */
|
|
|
|
var BARE_SUMMON_JS_MAX_SCRIPTS = 16
|
|
var BARE_SUMMON_JS_MAX_SOURCE = 256 * 1024
|
|
var BARE_SUMMON_JS_MAX_TIMERS = 32
|
|
|
|
function bareSummonResolveVm(ctx) {
|
|
if (ctx && typeof ctx.bareOsSummonEval === 'function') {
|
|
return { kind: 'syscall', eval: ctx.bareOsSummonEval }
|
|
}
|
|
var b = ctx && ctx.bare
|
|
var vm = b && (b.bareVm || b.vm)
|
|
if (vm && vm.default) vm = vm.default
|
|
if (
|
|
vm &&
|
|
typeof vm.createContext === 'function' &&
|
|
typeof vm.runInContext === 'function'
|
|
) {
|
|
return { kind: 'vm', vm: vm }
|
|
}
|
|
return { kind: 'function' }
|
|
}
|
|
|
|
function bareSummonMakeConsole(sink) {
|
|
function push(level, args) {
|
|
var msg = []
|
|
var i
|
|
for (i = 0; i < args.length; i++) {
|
|
try {
|
|
msg.push(typeof args[i] === 'string' ? args[i] : String(args[i]))
|
|
} catch (e) {
|
|
msg.push('[unprintable]')
|
|
}
|
|
}
|
|
sink.push({ level: level, text: msg.join(' ') })
|
|
}
|
|
return {
|
|
log: function () {
|
|
push('log', arguments)
|
|
},
|
|
info: function () {
|
|
push('info', arguments)
|
|
},
|
|
warn: function () {
|
|
push('warn', arguments)
|
|
},
|
|
error: function () {
|
|
push('error', arguments)
|
|
},
|
|
debug: function () {
|
|
push('debug', arguments)
|
|
}
|
|
}
|
|
}
|
|
|
|
function bareSummonMakeLocation(href) {
|
|
var u = bareSummonParseUrl(href) || {
|
|
href: String(href || ''),
|
|
protocol: '',
|
|
host: '',
|
|
hostname: '',
|
|
port: '',
|
|
pathname: '/',
|
|
search: '',
|
|
hash: '',
|
|
origin: ''
|
|
}
|
|
return {
|
|
href: u.href,
|
|
protocol: u.protocol,
|
|
host: u.host,
|
|
hostname: u.hostname,
|
|
port: u.port,
|
|
pathname: u.pathname,
|
|
search: u.search,
|
|
hash: u.hash,
|
|
origin: u.origin,
|
|
toString: function () {
|
|
return this.href
|
|
}
|
|
}
|
|
}
|
|
|
|
function bareSummonEvalSource(engine, source, sandbox) {
|
|
if (engine.kind === 'syscall') {
|
|
return engine.eval(source, sandbox)
|
|
}
|
|
if (engine.kind === 'vm') {
|
|
var box = engine.vm.createContext()
|
|
var k
|
|
for (k in sandbox) {
|
|
if (Object.prototype.hasOwnProperty.call(sandbox, k)) box[k] = sandbox[k]
|
|
}
|
|
return engine.vm.runInContext(
|
|
'var window = this; var self = this; var globalThis = this;\n' + source,
|
|
box
|
|
)
|
|
}
|
|
var fn = new Function(
|
|
'window',
|
|
'"use strict";' +
|
|
'var self = window;' +
|
|
'var document = window.document;' +
|
|
'var console = window.console;' +
|
|
'var location = window.location;' +
|
|
'var navigator = window.navigator;' +
|
|
'var setTimeout = window.setTimeout;' +
|
|
'var clearTimeout = window.clearTimeout;' +
|
|
'var fetch = window.fetch;' +
|
|
source
|
|
)
|
|
return fn(sandbox.window || sandbox)
|
|
}
|
|
|
|
async function bareSummonCollectScriptJobs(session, doc, pageUrl) {
|
|
var jobs = []
|
|
var nodes = (doc && doc.scripts) || []
|
|
var i
|
|
for (
|
|
i = 0;
|
|
i < nodes.length && jobs.length < BARE_SUMMON_JS_MAX_SCRIPTS;
|
|
i++
|
|
) {
|
|
var n = nodes[i]
|
|
var src = n.attrs && n.attrs.src
|
|
var type = ((n.attrs && n.attrs.type) || 'text/javascript').toLowerCase()
|
|
if (
|
|
type &&
|
|
type !== 'text/javascript' &&
|
|
type !== 'application/javascript' &&
|
|
type !== 'module' &&
|
|
type !== ''
|
|
) {
|
|
if (type.indexOf('javascript') < 0 && type !== 'module') continue
|
|
}
|
|
if (src) {
|
|
var abs = bareSummonResolveUrl(src, pageUrl)
|
|
if (!abs || !bareSummonIsHttp(abs)) continue
|
|
jobs.push({ node: n, url: abs.href, source: '', external: true })
|
|
} else {
|
|
var body = bareSummonScriptSource(n)
|
|
if (body)
|
|
jobs.push({ node: n, url: pageUrl, source: body, external: false })
|
|
}
|
|
}
|
|
for (i = 0; i < jobs.length; i++) {
|
|
if (!jobs[i].external) continue
|
|
var res = await bareSummonFetch(session.ctx, jobs[i].url, {
|
|
jar: session.jar,
|
|
maxBytes: BARE_SUMMON_JS_MAX_SOURCE
|
|
})
|
|
jobs[i].source = (res && res.body) || ''
|
|
if (jobs[i].source.length > BARE_SUMMON_JS_MAX_SOURCE) {
|
|
jobs[i].source = jobs[i].source.slice(0, BARE_SUMMON_JS_MAX_SOURCE)
|
|
}
|
|
}
|
|
return jobs
|
|
}
|
|
|
|
async function bareSummonRunDocumentJs(session, doc, pageUrl) {
|
|
var logs = []
|
|
var errors = []
|
|
var ran = 0
|
|
var engine = bareSummonResolveVm(session && session.ctx)
|
|
var timers = []
|
|
var tid = 0
|
|
var env = { console: bareSummonMakeConsole(logs) }
|
|
var document = bareSummonCreateDocumentApi(doc, env)
|
|
var location = bareSummonMakeLocation(pageUrl)
|
|
var window = {
|
|
document: document,
|
|
console: env.console,
|
|
location: location,
|
|
navigator: {
|
|
userAgent: 'Summon/0.2 (Bare OS; text; bare-vm)',
|
|
platform: 'bare',
|
|
language: 'en'
|
|
},
|
|
innerWidth: session.cols || 80,
|
|
innerHeight: 24,
|
|
devicePixelRatio: 1,
|
|
setTimeout: function (fn, ms) {
|
|
if (timers.length >= BARE_SUMMON_JS_MAX_TIMERS) return 0
|
|
tid++
|
|
timers.push({ id: tid, fn: fn, ms: ms | 0 })
|
|
return tid
|
|
},
|
|
clearTimeout: function (id) {
|
|
timers = timers.filter(function (t) {
|
|
return t.id !== id
|
|
})
|
|
},
|
|
fetch: function () {
|
|
return Promise.reject(new Error('summon: page fetch is not enabled'))
|
|
}
|
|
}
|
|
window.window = window
|
|
window.self = window
|
|
env.document = document
|
|
|
|
var jobs = await bareSummonCollectScriptJobs(session, doc, pageUrl)
|
|
var i
|
|
for (i = 0; i < jobs.length; i++) {
|
|
var src = jobs[i].source
|
|
if (!src || src.length > BARE_SUMMON_JS_MAX_SOURCE) continue
|
|
try {
|
|
bareSummonEvalSource(engine, src, {
|
|
window: window,
|
|
document: document,
|
|
console: env.console,
|
|
location: location,
|
|
navigator: window.navigator
|
|
})
|
|
ran++
|
|
} catch (err) {
|
|
errors.push({
|
|
url: jobs[i].url,
|
|
message: (err && err.message) || String(err)
|
|
})
|
|
env.console.error((err && err.message) || String(err))
|
|
}
|
|
}
|
|
for (i = 0; i < timers.length; i++) {
|
|
if (typeof timers[i].fn === 'function') {
|
|
try {
|
|
timers[i].fn.call(window)
|
|
} catch (e2) {
|
|
errors.push({ url: pageUrl, message: (e2 && e2.message) || String(e2) })
|
|
}
|
|
}
|
|
}
|
|
bareSummonFlushInlineStyle(doc.body)
|
|
bareSummonFlushInlineStyle(doc.head)
|
|
return {
|
|
engine: engine.kind,
|
|
ran: ran,
|
|
blocked: Math.max(0, ((doc.scripts && doc.scripts.length) || 0) - ran),
|
|
logs: logs,
|
|
errors: errors
|
|
}
|
|
}
|