111 lines · 4.4 KB
import { badRequest, forbidden } from "../errors";
import type { GitStore } from "../git/store";
import { MAX_POINTER_BYTES, parseLfsPointer } from "../lfs/pointer";

const MAX_ATTRIBUTES_BYTES = 64 * 1024;
const MAX_ATTRIBUTE_RULES = 500;
const decoder = new TextDecoder();

type Rule = { regex: RegExp; anchored: boolean; lfs: boolean };

const REGEX_SPECIAL = /[.+^${}()|\\]/;

/** gitattributes/fnmatch globs: `*` and `?` stay within a segment, `**` spans them, `[...]` is a class. */
function attributeGlob(pattern: string): RegExp {
  let source = "";
  for (let index = 0; index < pattern.length; index++) {
    const char = pattern[index];
    if (char === "*" && pattern[index + 1] === "*") {
      const slash = pattern[index + 2] === "/";
      source += slash ? "(?:.*/)?" : ".*";
      index += slash ? 2 : 1;
    } else if (char === "*") source += "[^/]*";
    else if (char === "?") source += "[^/]";
    else if (char === "[") {
      const close = pattern.indexOf("]", index + 2);
      if (close === -1) {
        source += "\\[";
        continue;
      }
      const body = pattern
        .slice(index + 1, close)
        .replace(/^!/, "^")
        .replace(/\\/g, "\\\\");
      source += `[${body}]`;
      index = close;
    } else if (char === "\\" && index + 1 < pattern.length) {
      index++;
      source += pattern[index].replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
    } else source += REGEX_SPECIAL.test(char) || char === "]" ? `\\${char}` : char;
  }