168 lines
4.6 KiB
JavaScript
168 lines
4.6 KiB
JavaScript
/** HTTP session for summon: redirects, cookies, caps. Uses ctx.httpFetch. */
|
|
|
|
var BARE_SUMMON_UA = 'Summon/0.1 (Bare OS; text)'
|
|
var BARE_SUMMON_MAX_REDIRECTS = 10
|
|
var BARE_SUMMON_DEFAULT_TIMEOUT = 20000
|
|
var BARE_SUMMON_DEFAULT_MAX_BYTES = 4 * 1024 * 1024
|
|
|
|
function bareSummonResolveFetch(ctx) {
|
|
if (ctx && typeof ctx.httpFetch === 'function') return ctx.httpFetch
|
|
if (ctx && ctx.bare && typeof ctx.bare.fetch === 'function')
|
|
return ctx.bare.fetch
|
|
if (typeof fetch === 'function') return fetch
|
|
return null
|
|
}
|
|
|
|
function bareSummonHeadersToObject(h) {
|
|
var out = {}
|
|
if (!h) return out
|
|
if (typeof h.forEach === 'function') {
|
|
h.forEach(function (v, k) {
|
|
out[String(k).toLowerCase()] = String(v)
|
|
})
|
|
return out
|
|
}
|
|
var k
|
|
for (k in h) {
|
|
if (Object.prototype.hasOwnProperty.call(h, k))
|
|
out[String(k).toLowerCase()] = String(h[k])
|
|
}
|
|
return out
|
|
}
|
|
|
|
function bareSummonReadSetCookies(headers, res) {
|
|
var out = []
|
|
if (
|
|
res &&
|
|
typeof res.headers !== 'undefined' &&
|
|
res.headers &&
|
|
typeof res.headers.getSetCookie === 'function'
|
|
) {
|
|
try {
|
|
var g = res.headers.getSetCookie()
|
|
if (Array.isArray(g)) return g
|
|
} catch (e) {
|
|
/* ignore */
|
|
}
|
|
}
|
|
var raw = headers['set-cookie']
|
|
if (!raw) return out
|
|
if (Array.isArray(raw)) return raw
|
|
return [String(raw)]
|
|
}
|
|
|
|
async function bareSummonFetch(ctx, urlStr, opts) {
|
|
opts = opts || {}
|
|
var fn = bareSummonResolveFetch(ctx)
|
|
if (!fn) {
|
|
return { ok: false, error: 'summon: no HTTP client (ctx.httpFetch)' }
|
|
}
|
|
var jar = opts.jar || bareSummonCreateCookieJar()
|
|
var method = String(opts.method || 'GET').toUpperCase()
|
|
var body = opts.body
|
|
var maxRedir =
|
|
opts.maxRedirects != null ? opts.maxRedirects : BARE_SUMMON_MAX_REDIRECTS
|
|
var timeout =
|
|
opts.timeoutMs != null ? opts.timeoutMs : BARE_SUMMON_DEFAULT_TIMEOUT
|
|
var maxBytes =
|
|
opts.maxBytes != null ? opts.maxBytes : BARE_SUMMON_DEFAULT_MAX_BYTES
|
|
var current = urlStr
|
|
var hops = 0
|
|
var lastStatus = 0
|
|
var headersOut = {}
|
|
var text = ''
|
|
var finalUrl = urlStr
|
|
|
|
while (hops <= maxRedir) {
|
|
var parsed = bareSummonParseUrl(current)
|
|
if (!parsed || !bareSummonIsHttp(parsed)) {
|
|
return { ok: false, error: 'summon: only http(s) URLs', url: current }
|
|
}
|
|
var hdr = {
|
|
'user-agent': BARE_SUMMON_UA,
|
|
accept: 'text/html,application/xhtml+xml;q=0.9,text/plain;q=0.8,*/*;q=0.1'
|
|
}
|
|
var cookie = jar.headerFor(current)
|
|
if (cookie) hdr.cookie = cookie
|
|
if (opts.headers) {
|
|
var hk
|
|
for (hk in opts.headers) {
|
|
if (Object.prototype.hasOwnProperty.call(opts.headers, hk)) {
|
|
hdr[hk] = opts.headers[hk]
|
|
}
|
|
}
|
|
}
|
|
var ac = null
|
|
var timer = null
|
|
if (typeof AbortController === 'function' && timeout > 0) {
|
|
ac = new AbortController()
|
|
timer = setTimeout(function () {
|
|
try {
|
|
ac.abort()
|
|
} catch (e) {
|
|
/* ignore */
|
|
}
|
|
}, timeout)
|
|
}
|
|
var res
|
|
try {
|
|
res = await fn(current, {
|
|
method: method,
|
|
headers: hdr,
|
|
body: method === 'GET' || method === 'HEAD' ? undefined : body,
|
|
redirect: 'manual',
|
|
signal: ac ? ac.signal : undefined
|
|
})
|
|
} catch (err) {
|
|
if (timer) clearTimeout(timer)
|
|
return {
|
|
ok: false,
|
|
error:
|
|
'summon: request failed: ' + ((err && err.message) || String(err)),
|
|
url: current
|
|
}
|
|
}
|
|
if (timer) clearTimeout(timer)
|
|
lastStatus = res && res.status ? res.status : 0
|
|
headersOut = bareSummonHeadersToObject(res && res.headers)
|
|
var setc = bareSummonReadSetCookies(headersOut, res)
|
|
for (var i = 0; i < setc.length; i++) jar.put(setc[i], current, opts)
|
|
var loc = headersOut.location
|
|
if (loc && lastStatus >= 300 && lastStatus < 400 && hops < maxRedir) {
|
|
var next = bareSummonResolveUrl(loc, current)
|
|
if (!next) break
|
|
if (
|
|
lastStatus === 303 ||
|
|
((lastStatus === 301 || lastStatus === 302) && method === 'POST')
|
|
) {
|
|
method = 'GET'
|
|
body = undefined
|
|
}
|
|
current = next.href
|
|
hops++
|
|
continue
|
|
}
|
|
finalUrl = current
|
|
if (typeof res.text === 'function') {
|
|
text = await res.text()
|
|
} else if (typeof res.arrayBuffer === 'function') {
|
|
var ab = await res.arrayBuffer()
|
|
text = new TextDecoder('utf-8', { fatal: false }).decode(
|
|
new Uint8Array(ab)
|
|
)
|
|
}
|
|
if (text && text.length > maxBytes) text = text.slice(0, maxBytes)
|
|
break
|
|
}
|
|
|
|
return {
|
|
ok: lastStatus >= 200 && lastStatus < 400,
|
|
status: lastStatus,
|
|
url: finalUrl,
|
|
headers: headersOut,
|
|
body: text,
|
|
jar: jar,
|
|
redirects: hops
|
|
}
|
|
}
|