dev: noUncheckedIndexedAccess
Massimo Melina committed
Jan 19, 2023 at 17:34 UTC
9fe44a7305f1e39b4f543654fcf1a1fea8710b60
15 files changed
+41
-34
src/QuickZipStream.ts
+2
-2
@@ -244,13 +244,13 @@ function buffer(pairs: number[]) {
244
assert(pairs.length % 2 === 0)
245
let total = 0
246
for (let i=0; i < pairs.length; i+=2)
247
- total += pairs[i]
247
+ total += pairs[i]!
248
const ret = Buffer.alloc(total, 0)
249
let offset = 0
250
let i = 0
251
while (i < pairs.length) {
252
const size = pairs[i++]
253
- const data = pairs[i++]
253
+ const data = pairs[i++]!
254
if (size === 1)
255
ret.writeUInt8(data, offset)
256
else if (size === 2)
src/adminApis.ts
+1
-3
@@ -141,12 +141,10 @@ export const adminApis: ApiHandlers = {
141
},
142
}
143
144
-for (const k in adminApis) {
145
- const was = adminApis[k]
144
+for (const [k, was] of Object.entries(adminApis))
145
adminApis[k] = (params, ctx) =>
146
ctxAdminAccess(ctx) ? was(params, ctx)
147
: new ApiError(HTTP_UNAUTHORIZED)
149
-}
148
149
export const localhostAdmin = defineConfig('localhost_admin', true)
150
src/api.auth.ts
+8
-2
@@ -6,11 +6,13 @@ import { ApiError, ApiHandler } from './apiMiddleware'
6
import { SRPParameters, SRPRoutines, SRPServerSession, SRPServerSessionStep1 } from 'tssrp6a'
7
import {
8
ADMIN_URI,
9
- HTTP_SERVER_ERROR,
9
SESSION_DURATION,
10
HTTP_UNAUTHORIZED,
11
HTTP_BAD_REQUEST,
13
- HTTP_NOT_ACCEPTABLE, HTTP_CONFLICT
12
+ HTTP_SERVER_ERROR,
13
+ HTTP_NOT_ACCEPTABLE,
14
+ HTTP_CONFLICT,
15
+ HTTP_NOT_FOUND
16
} from './const'
17
import { randomId } from './misc'
18
import Koa from 'koa'
@@ -82,6 +84,8 @@ export async function srpStep1(account: Account) {
84
if (!account.srp)
85
throw HTTP_NOT_ACCEPTABLE
86
const [salt, verifier] = account.srp.split('|')
87
+ if (!salt || !verifier)
88
+ throw Error("malformed account")
89
const srpSession = new SRPServerSession(srp6aNimbusRoutines)
90
const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier))
91
return { step1, salt, pubKey: String(step1.B) } // cast to string cause bigint can't be jsonized
@@ -94,6 +98,8 @@ export const loginSrp2: ApiHandler = async ({ pubKey, proof }, ctx) => {
98
return new ApiError(HTTP_CONFLICT)
99
const { username, sid } = ctx.session.login
100
const step1 = ongoingLogins[sid]
101
+ if (!step1)
102
+ return new ApiError(HTTP_NOT_FOUND)
103
try {
104
const M2 = await step1.step2(BigInt(pubKey), BigInt(proof))
105
await loggedIn(ctx, username)
src/apiMiddleware.ts
+3
-2
@@ -21,14 +21,15 @@ export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
21
return async (ctx) => {
22
const { params } = ctx
23
console.debug('API', ctx.method, ctx.path, { ...params })
24
- if (!apis.hasOwnProperty(ctx.path)) {
24
+ const apiFun = apis.hasOwnProperty(ctx.path) && apis[ctx.path]!
25
+ if (!apiFun) {
26
ctx.body = 'invalid api'
27
return ctx.status = HTTP_NOT_FOUND
28
}
29
const csrf = ctx.cookies.get('csrf')
30
// we don't rely on SameSite cookie option because it's https-only
31
let res = csrf && csrf !== params.csrf ? new ApiError(HTTP_UNAUTHORIZED, 'csrf')
31
- : await apis[ctx.path](params || {}, ctx)
32
+ : await apiFun(params || {}, ctx)
33
if (isAsyncGenerator(res))
34
res = asyncGeneratorToReadable(res)
35
if (res instanceof Readable) { // Readable, we'll go SSE-mode
src/commands.ts
+1
-1
@@ -25,7 +25,7 @@ catch {
25
function parseCommandLine(line: string) {
26
if (!line) return
27
const [name, ...params] = line.trim().split(/ +/)
28
- const cmd = (commands as any)[name]
28
+ const cmd = (commands as any)[name!]
29
if (!cmd)
30
return console.error("cannot understand entered command, try 'help'")
31
if (cmd.cb.length > params.length)
src/github.ts
+3
-1
@@ -7,7 +7,7 @@ import { getAvailablePlugins, mapPlugins, parsePluginSource, PATH as PLUGINS_PAT
7
import unzipper from 'unzip-stream'
8
import { ApiError } from './apiMiddleware'
9
import _ from 'lodash'
10
-import { HTTP_CONFLICT } from './const'
10
+import { HTTP_BAD_REQUEST, HTTP_CONFLICT } from './const'
11
12
const DIST_ROOT = 'dist/'
13
@@ -30,6 +30,8 @@ export async function downloadPlugin(repo: string, branch='', overwrite?: boolea
30
if (!branch)
31
branch = rec.default_branch
32
const short = repo.split('/')[1] // second part, repo without the owner
33
+ if (!short)
34
+ return new ApiError(HTTP_BAD_REQUEST, "bad repo")
35
const folder2repo = getFolder2repo()
36
const folder = overwrite ? _.findKey(folder2repo, x => x===repo)! // use existing folder
37
: short in folder2repo ? repo.replace('/','-') // longer form only if another plugin is using short form
src/listen.ts
+3
-2
@@ -209,8 +209,9 @@ function printUrls(port: number, proto: string) {
209
for (const [name, nets] of Object.entries(networkInterfaces())) {
210
if (!nets || ignore.test(name)) continue
211
_.remove(nets, 'internal')
212
- if (!nets.length) continue
213
- const best = _.find(nets, { family: 'IPv4' }) || nets[0]
212
+ const first = nets[0]
213
+ if (!first) continue
214
+ const best = _.find(nets, { family: 'IPv4' }) || first
215
const appendPort = port === (proto==='https' ? 443 : 80) ? '' : ':' + port
216
let { address } = best
217
if (address.includes(':'))
src/log.ts
+1
-1
@@ -92,7 +92,7 @@ export function log(): Koa.Middleware {
92
}
93
}
94
const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s\n' // Apache's Common Log Format
95
- const date = a[2]+'/'+a[1]+'/'+a[3]+':'+a[4]+' '+a[5].slice(3)
95
+ const date = a[2]+'/'+a[1]+'/'+a[3]+':'+a[4]+' '+a[5]?.slice(3)
96
const user = getCurrentUsername(ctx)
97
events.emit(logger.name, Object.assign(_.pick(ctx, ['ip', 'method','status','length']), { user, ts: now, uri: ctx.path }))
98
console.debug(ctx.status, ctx.method, ctx.path)
src/misc.ts
+1
-1
@@ -44,7 +44,7 @@ export function wantArray<T>(x?: void | T | T[]) {
44
}
45
46
export function getOrSet<T>(o: Record<string,T>, k:string, creator:()=>T): T {
47
- return k in o ? o[k]
47
+ return k in o ? o[k]!
48
: (o[k] = creator())
49
}
50
src/plugins.ts
+6
-8
@@ -78,9 +78,8 @@ export function pluginsMiddleware(): Koa.Middleware {
78
return async (ctx, next) => {
79
const after = []
80
// run middleware plugins
81
- for (const id in plugins)
81
+ for (const [id,pl] of Object.entries(plugins))
82
try {
83
- const pl = plugins[id]
83
const res = await pl.middleware?.(ctx)
84
if (res === true)
85
ctx.pluginStopped = true
@@ -96,7 +95,7 @@ export function pluginsMiddleware(): Koa.Middleware {
95
if (!ctx.pluginStopped) {
96
if (path.startsWith(PLUGINS_PUB_URI)) {
97
const a = path.substring(PLUGINS_PUB_URI.length).split('/')
99
- if (plugins.hasOwnProperty(a[0])) { // do it only if the plugin is loaded
98
+ if (plugins.hasOwnProperty(a[0]!)) { // do it only if the plugin is loaded
99
a.splice(1, 0, 'public')
100
await serveFile(PATH + '/' + a.join('/'), 'auto')(ctx, next)
101
}
@@ -206,7 +205,7 @@ export async function rescan() {
205
const foundDisabled: typeof availablePlugins = {}
206
const MASK = PATH + '/*/plugin.js' // be sure to not use path.join as fast-glob doesn't work with \
207
for (const f of await glob([adjustStaticPathForGlob(APP_PATH) + '/' + MASK, MASK])) {
209
- const id = f.split('/').slice(-2)[0]
208
+ const id = f.split('/').slice(-2)[0]!
209
if (id.endsWith(DISABLING_POSTFIX)) continue
210
if (!enablePlugins.get().includes(id)) {
211
try {
@@ -276,8 +275,7 @@ export async function rescan() {
275
}
276
})
277
}
279
- for (const id in foundDisabled) {
280
- const p = foundDisabled[id]
278
+ for (const [id,p] of Object.entries(foundDisabled)) {
279
const a = availablePlugins[id]
280
if (same(a, p)) continue
281
availablePlugins[id] = p
@@ -291,9 +289,9 @@ export async function rescan() {
289
delete availablePlugins[id]
290
events.emit('pluginUninstalled', id)
291
}
294
- for (const id in plugins)
292
+ for (const [id,p] of Object.entries(plugins))
293
if (!found.includes(id))
296
- await plugins[id].unload()
294
+ await p.unload()
295
}
296
297
function deleteModule(id: string) {
src/serveFile.ts
+4
-2
@@ -89,8 +89,10 @@ export function getRange(ctx: Koa.Context, totalSize: number) {
89
ctx.response.length = totalSize
90
return
91
}
92
- const ranges = range.split('=')[1]
93
- if (ranges.includes(','))
92
+ const [unit, ranges] = range.split('=')
93
+ if (unit !== 'bytes')
94
+ return ctx.throw(HTTP_BAD_REQUEST, 'bad range unit')
95
+ if (ranges?.includes(','))
96
return ctx.throw(HTTP_BAD_REQUEST, 'multi-range not supported')
97
let bytes = ranges?.split('-')
98
if (!bytes?.length)
src/update.ts
+2
-2
@@ -30,7 +30,7 @@ export async function update() {
30
throw "asset not found"
31
const url = asset.browser_download_url
32
console.log("downloading", url)
33
- const bin = process.argv[0]
33
+ const bin = process.argv0
34
const binPath = dirname(bin)
35
const binFile = basename(bin)
36
const newBinFile = 'new-' + binFile
@@ -63,7 +63,7 @@ export async function update() {
63
}
64
65
if (argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
66
- const bin = process.argv[0]
66
+ const bin = process.argv0
67
renameSync(bin, join(dirname(bin), argv.updating))
68
console.log("renamed binary file to", argv.updating)
69
}
src/vfs.ts
+4
-5
@@ -155,8 +155,7 @@ export async function* walkNode(parent:VfsNode, ctx?: Koa.Context, depth:number=
155
const { children, source } = parent
156
const took = prefixPath ? undefined : new Set()
157
if (children)
158
- for (let idx = 0; idx < children.length; idx++) {
159
- const child = children[idx]
158
+ for (const child of children) {
159
const name = prefixPath + getNodeName(child)
160
took?.add(name)
161
yield* workItem({
@@ -212,11 +211,11 @@ function inheritMasks(item: VfsNode, parent: VfsNode, virtualBasename:string) {
211
const { masks } = parent
212
if (!masks) return
213
const o: Masks = {}
215
- for (const k in masks)
214
+ for (const [k,v] of Object.entries(masks))
215
if (k.startsWith('**/'))
217
- o[k.slice(3)] = masks[k]
216
+ o[k.slice(3)] = v
217
else if (k.startsWith(virtualBasename+'/'))
219
- o[k.slice(virtualBasename.length+1)] = masks[k]
218
+ o[k.slice(virtualBasename.length+1)] = v
219
if (Object.keys(o).length)
220
item.masks = o
221
}
src/zip.ts
+1
-1
@@ -16,7 +16,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
16
ctx.mime = 'zip'
17
// ctx.query.list is undefined | string | string[]
18
const list = wantArray(ctx.query.list)[0]?.split('*') // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
19
- const name = list?.length === 1 ? basename(list[0]) : getNodeName(node)
19
+ const name = list?.length === 1 ? basename(list[0]!) : getNodeName(node)
20
ctx.attachment((isWindowsDrive(name) ? name[0] : (name || 'archive')) + '.zip')
21
const filter = pattern2filter(String(ctx.query.search||''))
22
const walker = !list ? walkNode(node, ctx, Infinity)
tsconfig.json
+1
-1
@@ -89,7 +89,7 @@
89
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
90
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
91
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
92
- // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92
+ "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
93
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
94
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
95
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */