Skip to content

Commit 3f584c3

Browse files
committed
fix(cli): safely decode keys
1 parent 75da91b commit 3f584c3

File tree

3 files changed

+18
-11
lines changed

3 files changed

+18
-11
lines changed

packages/cli/src/cli/cmd/purge.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getBuckets } from "../utils/buckets";
66
import { resolveOverriddenLocale } from "@lingo.dev/_spec";
77
import createBucketLoader from "../loaders";
88
import { minimatch } from "minimatch";
9+
import { safeDecode } from "../utils/key-matching";
910
import { confirm } from "@inquirer/prompts";
1011

1112
interface PurgeOptions {
@@ -123,10 +124,7 @@ export default new Command()
123124
if (options.key) {
124125
// minimatch for key patterns
125126
keysToRemove = Object.keys(newData).filter((k) =>
126-
minimatch(
127-
decodeURIComponent(k),
128-
decodeURIComponent(options.key!),
129-
),
127+
minimatch(safeDecode(k), safeDecode(options.key!)),
130128
);
131129
} else {
132130
// No key specified: remove all keys
@@ -136,7 +134,7 @@ export default new Command()
136134
// Show what will be deleted
137135
if (options.key) {
138136
bucketOra.info(
139-
`About to delete ${keysToRemove.length} key(s) matching '${decodeURIComponent(options.key)}' from ${bucketPath.pathPattern} [${targetLocale}]:\n ${keysToRemove.slice(0, 10).join(", ")}${keysToRemove.length > 10 ? ", ..." : ""}`,
137+
`About to delete ${keysToRemove.length} key(s) matching '${safeDecode(options.key)}' from ${bucketPath.pathPattern} [${targetLocale}]:\n ${keysToRemove.slice(0, 10).join(", ")}${keysToRemove.length > 10 ? ", ..." : ""}`,
140138
);
141139
} else {
142140
bucketOra.info(
@@ -164,7 +162,7 @@ export default new Command()
164162
await bucketLoader.push(targetLocale, newData);
165163
if (options.key) {
166164
bucketOra.succeed(
167-
`Removed ${keysToRemove.length} key(s) matching '${decodeURIComponent(options.key)}' from ${bucketPath.pathPattern} [${targetLocale}]`,
165+
`Removed ${keysToRemove.length} key(s) matching '${safeDecode(options.key)}' from ${bucketPath.pathPattern} [${targetLocale}]`,
168166
);
169167
} else {
170168
bucketOra.succeed(
@@ -173,7 +171,7 @@ export default new Command()
173171
}
174172
} else if (options.key) {
175173
bucketOra.info(
176-
`No keys matching '${decodeURIComponent(options.key)}' found in ${bucketPath.pathPattern} [${targetLocale}]`,
174+
`No keys matching '${safeDecode(options.key)}' found in ${bucketPath.pathPattern} [${targetLocale}]`,
177175
);
178176
} else {
179177
bucketOra.info("No keys to remove.");

packages/cli/src/cli/cmd/run/execute.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import pLimit, { LimitFunction } from "p-limit";
44
import _ from "lodash";
55
import { minimatch } from "minimatch";
66

7+
import { safeDecode } from "../../utils/key-matching";
78
import { colors } from "../../constants";
89
import { CmdRunContext, CmdRunTask, CmdRunTaskResult } from "./_types";
910
import { commonTaskRendererOptions } from "./_const";
@@ -231,10 +232,7 @@ function createWorkerTask(args: {
231232
([key]) =>
232233
!assignedTask.onlyKeys.length ||
233234
assignedTask.onlyKeys?.some((pattern) =>
234-
minimatch(
235-
decodeURIComponent(key),
236-
decodeURIComponent(pattern),
237-
),
235+
minimatch(safeDecode(key), safeDecode(pattern)),
238236
),
239237
)
240238
.fromPairs()

packages/cli/src/cli/utils/key-matching.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import { minimatch } from "minimatch";
22

3+
/**
4+
* Safely decodes a URI component, returning the original string if decoding fails.
5+
*/
6+
export function safeDecode(value: string): string {
7+
try {
8+
return decodeURIComponent(value);
9+
} catch {
10+
return value;
11+
}
12+
}
13+
314
/**
415
* Checks if a key matches any of the provided patterns using exact, separator-bounded prefix, or glob matching.
516
* Separator-bounded means the key must equal the pattern exactly, or continue with a ".", "/", or "-" separator.

0 commit comments

Comments
 (0)