fix old nix manpages, strip priv esc
This commit is contained in:
parent
bbd7c67d0c
commit
6da495dc59
2 changed files with 81 additions and 14 deletions
50
bin/main.ml
50
bin/main.ml
|
|
@ -762,6 +762,50 @@ let fuzzy_score needle haystack =
|
|||
) (0, 0, 0, -1) haystack_lc in
|
||||
if ni = nlen then score else 0
|
||||
|
||||
(* known privilege-escalation wrappers. when one of these is the first token,
|
||||
* we strip it (and its own options/arguments) before completing the real command.
|
||||
* options that consume the next token as an argument are listed per-command so
|
||||
* we don't accidentally treat the argument value as the real command name. *)
|
||||
let elevation_commands =
|
||||
["sudo"; "run0"; "doas"; "pkexec"; "su"; "calife"; "sux"; "sudoedit";
|
||||
"please"; "super"; "priv"]
|
||||
|
||||
let elevation_opts_with_arg = function
|
||||
| "sudo" -> ["-u"; "--user"; "-g"; "--group"; "-h"; "--host";
|
||||
"-p"; "--prompt"; "-r"; "--role"; "-t"; "--type";
|
||||
"-U"; "--other-user"; "-C"; "--close-from";
|
||||
"-T"; "--command-timeout"; "-D"; "--chdir"]
|
||||
| "run0" -> ["--unit"; "--property"; "--description"; "--slice";
|
||||
"--user"; "--group"; "--nice"; "--chdir"; "--setenv";
|
||||
"--background"; "--machine"]
|
||||
| "doas" -> ["-C"; "-u"]
|
||||
| "pkexec" -> ["--user"]
|
||||
| "su" -> ["-c"; "-s"; "-g"; "-G"]
|
||||
| _ -> []
|
||||
|
||||
(* strip the elevation command itself plus all its own flags/options from
|
||||
* the span list, returning the spans for the real command (or [] if none).
|
||||
* handles:
|
||||
* --flag=value (value is part of the token — no extra token consumed)
|
||||
* --flag value (value is the next token — consumed if flag is in the list)
|
||||
* -- (end-of-options sentinel — everything after is the command) *)
|
||||
let strip_elevation cmd args =
|
||||
let with_arg = elevation_opts_with_arg cmd in
|
||||
let rec skip = function
|
||||
| [] -> []
|
||||
| "--" :: rest -> rest
|
||||
| arg :: rest when String.length arg > 0 && arg.[0] = '-' ->
|
||||
if List.mem arg with_arg then
|
||||
(* option takes the next token as its argument — drop both *)
|
||||
skip (match rest with _ :: tl -> tl | [] -> [])
|
||||
else begin
|
||||
(* boolean flag or --flag=value form — drop only this token *)
|
||||
skip rest
|
||||
end
|
||||
| real_cmd -> real_cmd
|
||||
in
|
||||
skip args
|
||||
|
||||
(* "inshellah complete CMD [ARGS...]" — the nushell custom completer.
|
||||
* this is the hot path — called every time the user presses tab in nushell.
|
||||
*
|
||||
|
|
@ -785,6 +829,12 @@ let fuzzy_score needle haystack =
|
|||
* prevents showing sibling subcommands when the user has already committed
|
||||
* to a specific subcommand path. *)
|
||||
let cmd_complete spans user_dir system_dirs =
|
||||
(* if the command line starts with a privilege-escalation wrapper, strip the
|
||||
* wrapper and all its own options so we complete the real command instead. *)
|
||||
let spans = match spans with
|
||||
| cmd :: rest when List.mem cmd elevation_commands ->
|
||||
strip_elevation cmd rest
|
||||
| _ -> spans in
|
||||
match spans with
|
||||
| [] -> print_string "[]\n"
|
||||
| cmd_name :: rest ->
|
||||
|
|
|
|||
|
|
@ -269,15 +269,23 @@ let classify_line line =
|
|||
(* --- section extraction ---
|
||||
* manpages are divided into sections by .SH macros. the options section
|
||||
* contains the flag definitions we want. if there's no OPTIONS section,
|
||||
* we fall back to DESCRIPTION (some simple tools put flags there). *)
|
||||
* we fall back to DESCRIPTION (some simple tools put flags there).
|
||||
*
|
||||
* old-style nix manpages (nix-build, nix-env-install, etc.) split flags
|
||||
* across multiple .SH sections with option-like names: e.g. "Options" for
|
||||
* command-specific flags and "Common Options" for flags shared by all nix
|
||||
* commands. collecting only the first such section misses the majority of
|
||||
* flags, so we collect and concatenate ALL option-like sections. *)
|
||||
|
||||
let extract_options_section lines =
|
||||
let classified = List.map classify_line lines in
|
||||
let rec collect_until_next_sh lines acc =
|
||||
(* collect lines until the next .SH header, returning (content, rest)
|
||||
* where rest starts at the .SH line (or is empty if at end of file). *)
|
||||
let rec collect_section lines acc =
|
||||
match lines with
|
||||
| [] -> List.rev acc
|
||||
| Macro ("SH", _) :: _ -> List.rev acc
|
||||
| line :: rest -> collect_until_next_sh rest (line :: acc)
|
||||
| [] -> (List.rev acc, [])
|
||||
| Macro ("SH", _) :: _ -> (List.rev acc, lines)
|
||||
| line :: rest -> collect_section rest (line :: acc)
|
||||
in
|
||||
let is_options_section name =
|
||||
let s = String.uppercase_ascii (String.trim name) in
|
||||
|
|
@ -286,24 +294,33 @@ let extract_options_section lines =
|
|||
try let _ = Str.search_forward (Str.regexp_string "OPTION") s 0 in true
|
||||
with Not_found -> false)
|
||||
in
|
||||
(* First pass: look for OPTIONS section *)
|
||||
let rec find_options = function
|
||||
| [] -> None
|
||||
(* Collect from ALL option-like .SH sections and concatenate them.
|
||||
* handles the common nix pattern where "Options" and "Common Options"
|
||||
* are separate .SH sections but both contain relevant flags.
|
||||
*
|
||||
* a synthetic Macro("SH","") separator is inserted between sections so
|
||||
* that collect_desc_text (which stops on SH/SS) does not let a description
|
||||
* from the last entry in one section bleed into the intro text of the next. *)
|
||||
let rec find_all_options lines acc =
|
||||
match lines with
|
||||
| [] -> acc
|
||||
| Macro ("SH", args) :: rest when is_options_section args ->
|
||||
Some (collect_until_next_sh rest [])
|
||||
| _ :: rest -> find_options rest
|
||||
let (section, remaining) = collect_section rest [] in
|
||||
let sep = if acc = [] then [] else [Macro ("SH", "")] in
|
||||
find_all_options remaining (acc @ sep @ section)
|
||||
| _ :: rest -> find_all_options rest acc
|
||||
in
|
||||
(* Fallback: DESCRIPTION section *)
|
||||
let rec find_description = function
|
||||
| [] -> []
|
||||
| Macro ("SH", args) :: rest
|
||||
when String.uppercase_ascii (String.trim args) = "DESCRIPTION" ->
|
||||
collect_until_next_sh rest []
|
||||
fst (collect_section rest [])
|
||||
| _ :: rest -> find_description rest
|
||||
in
|
||||
match find_options classified with
|
||||
| Some section -> section
|
||||
| None -> find_description classified
|
||||
match find_all_options classified [] with
|
||||
| [] -> find_description classified
|
||||
| sections -> sections
|
||||
|
||||
(* --- strategy-based entry extraction ---
|
||||
* rather than a single monolithic parser, we use multiple "strategies" that
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue