1 | import { promises as fs } from 'fs'
|
2 |
|
3 | import pathExists from 'path-exists'
|
4 |
|
5 | import { splitResults } from './results.js'
|
6 | import { isUrl } from './url.js'
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | export const parseFileRedirects = async function (redirectFile) {
|
27 | const results = await parseRedirects(redirectFile)
|
28 | return splitResults(results)
|
29 | }
|
30 |
|
31 | const parseRedirects = async function (redirectFile) {
|
32 | if (!(await pathExists(redirectFile))) {
|
33 | return []
|
34 | }
|
35 |
|
36 | const text = await readRedirectFile(redirectFile)
|
37 | if (typeof text !== 'string') {
|
38 | return [text]
|
39 | }
|
40 | return text.split('\n').map(normalizeLine).filter(hasRedirect).map(parseRedirect)
|
41 | }
|
42 |
|
43 | const readRedirectFile = async function (redirectFile) {
|
44 | try {
|
45 | return await fs.readFile(redirectFile, 'utf8')
|
46 | } catch {
|
47 | return new Error(`Could not read redirects file: ${redirectFile}`)
|
48 | }
|
49 | }
|
50 |
|
51 | const normalizeLine = function (line, index) {
|
52 | return { line: line.trim(), index }
|
53 | }
|
54 |
|
55 | const hasRedirect = function ({ line }) {
|
56 | return line !== '' && !isComment(line)
|
57 | }
|
58 |
|
59 | const parseRedirect = function ({ line, index }) {
|
60 | try {
|
61 | return parseRedirectLine(line)
|
62 | } catch (error) {
|
63 | return new Error(`Could not parse redirect line ${index + 1}:
|
64 | ${line}
|
65 | ${error.message}`)
|
66 | }
|
67 | }
|
68 |
|
69 |
|
70 | const parseRedirectLine = function (line) {
|
71 | const [from, ...parts] = trimComment(line.split(LINE_TOKENS_REGEXP))
|
72 |
|
73 | if (parts.length === 0) {
|
74 | throw new Error('Missing destination path/URL')
|
75 | }
|
76 |
|
77 | const {
|
78 | queryParts,
|
79 | to,
|
80 | lastParts: [statusPart, ...conditionsParts],
|
81 | } = parseParts(from, parts)
|
82 |
|
83 | const query = parsePairs(queryParts)
|
84 | const { status, force } = parseStatus(statusPart)
|
85 | const { Sign, signed = Sign, ...conditions } = parsePairs(conditionsParts)
|
86 | return { from, query, to, status, force, conditions, signed }
|
87 | }
|
88 |
|
89 |
|
90 | const trimComment = function (parts) {
|
91 | const commentIndex = parts.findIndex(isComment)
|
92 | return commentIndex === -1 ? parts : parts.slice(0, commentIndex)
|
93 | }
|
94 |
|
95 | const isComment = function (part) {
|
96 | return part.startsWith('#')
|
97 | }
|
98 |
|
99 | const LINE_TOKENS_REGEXP = /\s+/g
|
100 |
|
101 |
|
102 |
|
103 | const parseParts = function (from, parts) {
|
104 |
|
105 |
|
106 |
|
107 | if (isStatusCode(parts[0])) {
|
108 | return { queryParts: [], to: undefined, lastParts: parts }
|
109 | }
|
110 |
|
111 | const toIndex = parts.findIndex(isToPart)
|
112 | if (toIndex === -1) {
|
113 | throw new Error('The destination path/URL must start with "/", "http:" or "https:"')
|
114 | }
|
115 |
|
116 | const queryParts = parts.slice(0, toIndex)
|
117 | const to = parts[toIndex]
|
118 | const lastParts = parts.slice(toIndex + 1)
|
119 | return { queryParts, to, lastParts }
|
120 | }
|
121 |
|
122 | const isToPart = function (part) {
|
123 | return part.startsWith('/') || isUrl(part)
|
124 | }
|
125 |
|
126 | const isStatusCode = function (part) {
|
127 | return Number.isInteger(getStatusCode(part))
|
128 | }
|
129 |
|
130 |
|
131 | const parseStatus = function (statusPart) {
|
132 | if (statusPart === undefined) {
|
133 | return {}
|
134 | }
|
135 |
|
136 | const status = getStatusCode(statusPart)
|
137 | const force = statusPart.endsWith('!')
|
138 | return { status, force }
|
139 | }
|
140 |
|
141 | const getStatusCode = function (statusPart) {
|
142 | return Number.parseInt(statusPart)
|
143 | }
|
144 |
|
145 |
|
146 | const parsePairs = function (conditions) {
|
147 | return Object.assign({}, ...conditions.map(parsePair))
|
148 | }
|
149 |
|
150 | const parsePair = function (condition) {
|
151 | const [key, value] = condition.split('=')
|
152 | return { [key]: value }
|
153 | }
|