| 1 | import { HttpStatus } from '@nestjs/common'; |
| 2 | |
| 3 | import { DomainError } |
| 4 | |
| 5 | /** Generous, but far past anything git itself will hold. */ |
| 6 | export const MAX_TREE_PATH_LENGTH = |
| 7 | export const MAX_TREE_PATH_DEPTH = |
| 8 | |
| 9 | /** NUL survives percent-decoding and would terminate the argument git actually sees; the other control characters have no business in a URL query. */ |
| 10 | // eslint-disable-next-line no-control-regex |
| 11 | const CONTROL_CHARACTERS = / |
| 12 | |
| 13 | export class UnsafeTreePathError |
| 14 | readonly status = |
| 15 | |
| 16 | constructor(reason: |
| 17 | super(`Invalid repository path: ${reason |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | /** |
| 22 | * Whether a browser-supplied path is a plain repository-relative directory. |
| 23 | * |
| 24 | * Express has already percent-decoded the query value once, so this runs on the real characters - decoding again here would turn `%252e%252e` into `..` and hand back the traversal this is meant to reject. |
| 25 | */ |
| 26 | export function isSafeTreePath(input |
| 27 | if (typeof input |
| 28 | try { |
| 29 | normalizeTreePath(input); |
| 30 | return true; |
| 31 | } catch { |
| 32 | return false; |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * Normalizes a path to the trailing-slash prefix form a listing uses ("" at the repository root), rejecting anything that is not a literal subdirectory. |
| 38 | * |
| 39 | * The path becomes a git pathspec and never touches the filesystem, but a traversal deserves a 400 rather than an escaping `fatal:` from git. |
| 40 | */ |
| 41 | export function normalizeTreePath(input |
| 42 | if (input.length |
| 43 | throw new UnsafeTreePathError( |
| 44 | } |
| 45 | if (CONTROL_CHARACTERS.test |
| 46 | throw new UnsafeTreePathError( |
| 47 | } |
| 48 | |
| 49 | const trimmed = |
| 50 | if (!trimmed) |
| 51 | |
| 52 | const segments = |
| 53 | if (segments.length |
| 54 | throw new UnsafeTreePathError( |
| 55 | } |
| 56 | |
| 57 | for (const segment |
| 58 | // "" is a doubled slash, which git would never have produced itself |
| 59 | if (!segment) |
| 60 | if (segment === |
| 61 | throw new UnsafeTreePathError( |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | return `${segments. |
| 66 | } |
| 67 | |
| 68 | /** The file form of {@ |
| 69 | export function normalizeBlobPath(input |
| 70 | const prefix = |
| 71 | if (!prefix) |
| 72 | return prefix.slice |
| 73 | } |