| 1 | import { getCurrentUsername, setLoggedIn } from './auth' |
| 2 | import { BASIC_AUTHENTICATE_HEADER, HTTP_UNAUTHORIZED } from './cross-const' |
| 3 | import Koa from 'koa' |
| 4 | import { defineConfig } from './config' |
| 5 | import { getNodeName, getDefaultFile, nodeIsFolder, VfsNode, walkNode } from './vfs' |
| 6 | import { asyncGeneratorToReadable, Dict, escapeHTML, filterMapGenerator, pathEncode } from './misc' |
| 7 | import _ from 'lodash' |
| 8 | import { title } from './adminApis' |
| 9 | import { getSection } from './customHtml' |
| 10 | |
| 11 | const autoBasic = defineConfig<boolean|string, null|RegExp>('auto_basic', true, v => _.isString(v) ? new RegExp(v, 'i') : null) |
| 12 | |
| 13 | export function basicWeb(ctx: Koa.Context, node: VfsNode) { |
| 14 | const { get } = ctx.query |
| 15 | if (get === 'login') { |
| 16 | if (getCurrentUsername(ctx)) |
| 17 | ctx.redirect(ctx.get('referer')) |
| 18 | else { |
| 19 | ctx.set('WWW-Authenticate', BASIC_AUTHENTICATE_HEADER) |
| 20 | ctx.status = HTTP_UNAUTHORIZED |
| 21 | } |
| 22 | return true |
| 23 | } |
| 24 | if (get === 'logout') { |
| 25 | ctx.body = `<script>location = ${JSON.stringify(ctx.get('referer'))}</script>` |
| 26 | setLoggedIn(ctx, false) |
| 27 | ctx.status = HTTP_UNAUTHORIZED // not effective on firefox52, but the redirection is |
| 28 | return true |
| 29 | } |
| 30 | const forced = get === 'basic' |
| 31 | const goBasic = forced || detectBasicAgent(ctx) && get !== 'nobasic' |
| 32 | if (!goBasic) return |
| 33 | ctx.type = 'html' |
| 34 | const force = forced ? '?get=basic' : '' |
| 35 | const walker = walkNode(node, { ctx, depth: 0 }) |
| 36 | const stream = asyncGeneratorToReadable(filterMapGenerator(walker, async el => { |
| 37 | const isFolder = nodeIsFolder(el) |
| 38 | const name = getNodeName(el) + (isFolder ? '/' : '') |
| 39 | return `<li>${a(pathEncode(name) + (isFolder && !await getDefaultFile(el, ctx) ? force : ''), name)}\n` |
| 40 | })) |
| 41 | ctx.body = stream |
| 42 | stream.push(`<meta name="viewport" content="width=device-width" />`) |
| 43 | stream.push(`<style>body { font-size: 16pt; }</style>`) |
| 44 | stream.push(`<title>${title.get()}</title><body>`) |
| 45 | stream.push(getSection('basicHeader')) |
| 46 | const u = getCurrentUsername(ctx) |
| 47 | const links: Dict<string> = u ? { [`//LOGOUT%00:@${ctx.host}/?get=logout`]: `Logout (${u})` } : { '/?get=login': "Login" } |
| 48 | stream.push(_.map(links, (v,k) => a(k, v)).join(' ') + '\n<ul>\n') |
| 49 | if (ctx.state.originalPath.length > 1) |
| 50 | stream.push('<li>' + a('..' + force, '..') + '\n') |
| 51 | stream.on('ending', () => |
| 52 | stream.push('</ul>\n' + getSection('basicFooter')) ) |
| 53 | return true |
| 54 | |
| 55 | function a(href: string, label: string) { |
| 56 | return `<a href='${href}'>${escapeHTML(label)}</a>` |
| 57 | } |
| 58 | |
| 59 | } |
| 60 | |
| 61 | export function detectBasicAgent(ctx: Koa.Context) { |
| 62 | const ua = ctx.get('user-agent') |
| 63 | return autoBasic.get() && /^$|Mozilla\/4|WebKit\/([234]\d\d|5[012]\d|53[0123456])[. ]|Trident|Lynx|curl|Firefox\/(\d|[1234]\d)\./.test(ua) |
| 64 | || autoBasic.compiled()?.test(ua) |
| 65 | } |