Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 37x 37x 37x 37x 37x 108x 3x 105x 144x 141x 141x 148x 7x 141x 9x 141x 7x 134x 249x 2675x 2675x 2429x | import { encodeApiSegment } from '../../utils/api-path';
export const ARTIFACT_DEFAULT_BYTES = 64 * 1024;
export const ARTIFACT_MAX_BYTES = 1024 * 1024;
export const ARTIFACT_MAX_OFFSET = 20 * 1024 * 1024;
/** Keep a nested archive path relative and unambiguous through URL and Rails decoding. */
export function encodeArtifactPath(path: string): string {
if (!path || path.length > 4096 || path.includes('\\') || hasControl(path)) {
throw new Error('Artifact path must be a relative file path without control characters');
}
return path
.split('/')
.map((segment) => {
if (!segment) throw new Error('Artifact path cannot contain empty segments');
let decoded = segment;
for (;;) {
if (decoded === '.' || decoded === '..' || /[/\\]/.test(decoded) || hasControl(decoded)) {
throw new Error('Artifact path cannot contain traversal or encoded separators');
}
// Only ASCII escapes alter path syntax; other percent sequences are literal filenames.
const next = decoded.replace(/%([0-7][0-9a-f])/gi, (_encoded, hex: string) =>
String.fromCodePoint(Number.parseInt(hex, 16)),
);
if (next === decoded) break;
decoded = next;
}
return encodeApiSegment(segment);
})
.join('/');
}
function hasControl(value: string): boolean {
for (let index = 0; ; index++) {
const code = value.codePointAt(index);
if (code === undefined) return false;
if (code < 32 || code === 127) return true;
}
}
|