All files / shared input-limits.ts

94.11% Statements 16/17
92.3% Branches 12/13
100% Functions 3/3
100% Lines 16/16

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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118              170x                                                                                           170x         170x         89x       38x   51x 51x 3x   48x       24x         2x   22x     170x                                                   43x 43x 43x    
/**
 * Authoritative limits for data crossing the REST and WebSocket boundary.
 *
 * Keep this module dependency-free so client and server schemas share one
 * contract. Values are intentionally expressed in bytes where payload size is
 * relevant; JavaScript string limits are measured in UTF-16 code units by Zod.
 */
export const INPUT_LIMITS = {
  rest: {
    maxBodyBytes: 1024 * 1024,
  },
  compile: {
    maxCodeChars: 128 * 1024,
    maxHeaders: 20,
    maxHeaderNameChars: 128,
    maxHeaderContentChars: 32 * 1024,
    maxLibraries: 20,
    maxLibraryNameChars: 128,
    maxFqbnChars: 128,
  },
  tutor: {
    maxCredentialChars: 512,
    maxAnswerChars: 2_000,
    maxFeedbackChars: 1_000,
    maxHistoryEntries: 8,
  },
  examples: {
    maxRepositoryOwnerChars: 39,
    maxRepositoryNameChars: 100,
    maxRepositorySlugChars: 140,
    maxRefChars: 128,
    fullCommitShaChars: 40,
  },
  webSocket: {
    maxPayloadBytes: 256 * 1024,
    maxSerialInputChars: 4 * 1024,
    maxTestRunIdChars: 64,
  },
  simulation: {
    minTimeoutSeconds: 1,
    defaultTimeoutSeconds: 60,
    maxTimeoutSeconds: 300,
    minPin: 0,
    maxPin: 19,
    // Arduino UNO ADC readings use the full 10-bit range. Digital writes use
    // only 0/1, but this shared WebSocket command also carries analog inputs.
    maxPinValue: 1023,
    minBaudrate: 300,
    maxBaudrate: 115_200,
  },
} as const;
 
/** URL-safe identifier accepted for test-only artifact namespaces. */
export const TEST_RUN_ID_PATTERN = new RegExp(
  `^[A-Za-z0-9_-]{1,${INPUT_LIMITS.webSocket.maxTestRunIdChars}}$`,
);
 
/** Portable header basename; separators and traversal tokens cannot match. */
export const HEADER_NAME_PATTERN = new RegExp(
  `^[A-Za-z0-9][A-Za-z0-9_.-]{0,${INPUT_LIMITS.compile.maxHeaderNameChars - 1}}$`,
);
 
export function normalizeSimulationTimeout(timeoutSeconds: number | undefined): number {
  if (
    timeoutSeconds === undefined ||
    !Number.isFinite(timeoutSeconds)
  ) {
    return INPUT_LIMITS.simulation.defaultTimeoutSeconds;
  }
  const roundedTimeout = Math.floor(timeoutSeconds);
  if (roundedTimeout < INPUT_LIMITS.simulation.minTimeoutSeconds) {
    return INPUT_LIMITS.simulation.defaultTimeoutSeconds;
  }
  return Math.min(roundedTimeout, INPUT_LIMITS.simulation.maxTimeoutSeconds);
}
 
export function normalizeBaudrate(baudrate: number): number {
  if (
    !Number.isInteger(baudrate) ||
    baudrate < INPUT_LIMITS.simulation.minBaudrate ||
    baudrate > INPUT_LIMITS.simulation.maxBaudrate
  ) {
    return 9600;
  }
  return baudrate;
}
 
const WINDOWS_RESERVED_BASENAMES = new Set([
  "CON",
  "PRN",
  "AUX",
  "NUL",
  "COM1",
  "COM2",
  "COM3",
  "COM4",
  "COM5",
  "COM6",
  "COM7",
  "COM8",
  "COM9",
  "LPT1",
  "LPT2",
  "LPT3",
  "LPT4",
  "LPT5",
  "LPT6",
  "LPT7",
  "LPT8",
  "LPT9",
]);
 
export function isSafeHeaderName(name: string): boolean {
  Iif (!HEADER_NAME_PATTERN.test(name)) return false;
  const basename = name.split(".")[0]?.toUpperCase();
  return !WINDOWS_RESERVED_BASENAMES.has(basename);
}