package util import ( "regexp" "strings" ) // GlobToRegex converts a glob pattern to a Seatbelt-compatible regular expression. // // This implements gitignore-style pattern matching to match the behavior used // in filesystem permission systems. // // Supported patterns: // - * matches any characters except / (e.g., *.ts matches foo.ts but not foo/bar.ts) // - ** matches any characters including / (e.g., src/**/*.ts matches all .ts files in src/) // - ? matches any single character except / (e.g., file?.txt matches file1.txt) // - [abc] matches any character in the set (e.g., file[0-9].txt matches file3.txt) // // Note: This is designed for macOS sandbox (regex ...) syntax. The resulting regex // will be used in sandbox profiles like: (deny file-write* (regex "pattern")) // // Examples: // - "/path/to/*.txt" -> "^/path/to/[^/]*\\.txt$" // - "/path/**/file" -> "^/path/(.*/)?file$" // - "/tmp/file?.log" -> "^/tmp/file[^/]\\.log$" func GlobToRegex(globPattern string) string { result := normalizeGlobPathSeparator(globPattern) // Escape regex special characters (except glob chars * ? [ ]) // We need to escape: . ^ $ + { } ( ) | \ result = escapeRegexChars(result) // Escape unclosed brackets (no matching ]) // This handles edge cases like "[abc" which should be treated literally result = escapeUnclosedBrackets(result) // Convert glob patterns to regex (order matters - ** before *) // Use placeholders to avoid double-conversion // 1. Handle **/ (globstar with slash) result = strings.ReplaceAll(result, "**/", "__GLOBSTAR_SLASH__") // 2. Handle ** (globstar standalone) result = strings.ReplaceAll(result, "**", "__GLOBSTAR__") // 3. Handle * (wildcard) result = strings.ReplaceAll(result, "*", "[^/]*") // 4. Handle ? (single char wildcard) result = strings.ReplaceAll(result, "?", "[^/]") // 5. Restore placeholders result = strings.ReplaceAll(result, "__GLOBSTAR_SLASH__", "(.*/)?") result = strings.ReplaceAll(result, "__GLOBSTAR__", ".*") // Add anchors for exact matching return "^" + result + "$" } // escapeRegexChars escapes regex special characters except glob wildcards. // Escapes: . ^ $ + { } ( ) | // Preserves: * ? [ ] \ // Note: We don't escape backslash because it shouldn't appear in file path glob patterns func escapeRegexChars(s string) string { // Characters that need escaping in regex (excluding glob chars) // We don't include backslash here because: // 1. File paths on Unix don't contain backslashes // 2. We use backslash to escape regex chars, so escaping backslash would double them specialChars := []string{".", "^", "$", "+", "{", "}", "(", ")", "|"} result := s for _, char := range specialChars { result = strings.ReplaceAll(result, char, "\\"+char) } return result } // normalizeGlobPathSeparator normalizes the path separator for a glob pattern. // This is to keep options open to normalize the path separator when we support Windows. // Particularly for Windows, the path separator is '\\' instead of '/'. We will normalize // the path separator to '/' for consistency. func normalizeGlobPathSeparator(s string) string { return s } var escapeUnclosedBracketsRegex = regexp.MustCompile(`\[([^\]]*?)$`) // escapeUnclosedBrackets escapes bracket expressions that don't have a closing bracket. // Example: "[abc" -> "\[abc" func escapeUnclosedBrackets(s string) string { // Find all opening brackets that don't have a closing bracket return escapeUnclosedBracketsRegex.ReplaceAllString(s, `\[$1`) }