-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCliOutputGuard.cs
More file actions
193 lines (177 loc) · 8.05 KB
/
Copy pathCliOutputGuard.cs
File metadata and controls
193 lines (177 loc) · 8.05 KB
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
namespace XISOSharp.Cli;
using Serilog;
using Logging;
/// <summary>
/// Input==output refusal checks (TODO #15, xdvdfs #36). Each check returns the
/// error line to print, or <c>null</c> when the output is safe. Kept in one
/// place so the per-mode wiring stays a two-liner and the rules are unit
/// testable; the streaming library APIs enforce the same rules as a backstop
/// (they throw <see cref="IOException"/>), but these pre-checks fire before
/// any prompt, move, or byte is touched.
/// </summary>
internal static class CliOutputGuard
{
/// <summary>
/// Flags the upstream #61 confusion: a known option spelling sitting in a
/// positional slot (the main parser stops at the first non-flag token, so
/// <c>game.iso -d ./new/</c> would otherwise be probed as a file named
/// <c>-d</c>). Returns the error line, or <c>null</c> for anything that is
/// not an exact known-flag spelling. CLI-025: there is no existence
/// bypass — a file literally named like a flag stays reachable as
/// <c>./-y</c> or by absolute path.
/// </summary>
public static string? CheckMisplacedFlag(string? token)
{
try
{
if (string.IsNullOrEmpty(token) || !MisplacedFlags.Contains(token))
return null;
return $"Error: {token} must come before ISO filenames" +
$" (e.g. -x {token} <value> game.iso);" +
" a flag after the first filename is read as a filename" +
" (use ./-flag if a file is really named this way)\n";
}
catch (Exception ex)
{
Log.Error(ex, "CheckMisplacedFlag failed");
BugReporter.ReportException(ex, "CheckMisplacedFlag failed");
return null;
}
}
// CLI-023: covers every main-parser flag spelling (--xbe-info, --repair,
// --salvage, --repair-out, --file-time included). -O/--output are verb-only
// spellings, deliberately absent: warning about them here would misadvise.
private static readonly HashSet<string> MisplacedFlags = new(StringComparer.Ordinal)
{
"-v", "-h", "--help", "-c", "-x", "--unpack", "-X", "-l", "-t", "-i",
"--ls", "--xex-info", "--xbe-info", "--md5", "--sha256", "-V", "validate",
"--validate", "--validate-checksums", "--validate-strict",
"--validate-report", "--copy-out", "--copy-in", "--no-backup", "-r", "-q", "-Q", "-s", "-D",
"-m", "-y", "--yes", "-n", "--no", "-d", "-o",
"--skip-sectors", "--prepend-sectors", "--batch",
"--batch-recursive", "--skip-existing", "--continue-on-error", "--pack", "--video",
"--random", "--seed", "--wipe", "--trim", "--petrify", "--update",
"--zar", "--all", "--best", "--compress", "--security-sectors",
"--sectors", "--checksum", "--filetime", "--get-filetime", "--file-time",
"--set-filetime", "--sector-layout", "--ranges", "--is-optimized",
"--silent", "--dry-run", "--jobs", "--policy",
"--repair", "--salvage", "--repair-out",
};
/// <summary>
/// Rewrite (<c>-r</c>) runs <c>input → input.old</c> before writing, so an
/// <c>-o</c> pointing at the input itself (or at the backup about to hold
/// it) is refused: the former is just the default, the latter destroys data.
/// </summary>
public static string? CheckRewriteOutput(string xisoPath, string? outputName)
{
try
{
if (string.IsNullOrWhiteSpace(outputName))
return null;
if (XisoPaths.AreSamePath(xisoPath, outputName))
{
return $"Error: rewrite output {outputName} is the same file as the input;" +
" omit -o to rewrite in place\n";
}
if (XisoPaths.AreSamePath(xisoPath + ".old", outputName))
{
return $"Error: rewrite output {outputName} would overwrite the {xisoPath}.old backup;" +
" choose another name\n";
}
return null;
}
catch (Exception ex)
{
Log.Error(ex, "CheckRewriteOutput failed for {Input}", xisoPath);
BugReporter.ReportException(ex, $"CheckRewriteOutput failed for {xisoPath}");
// CLI-024: fail closed — an unverifiable comparison must block the
// overwrite, not wave it through (null reads as safe).
return
$"Error: could not verify rewrite output {outputName ?? "<none>"} against {xisoPath} ({ex.Message}); refusing to overwrite\n";
}
}
/// <summary>
/// Single-input <c>-o</c> used by the redump batch modes (video/random/seed/
/// wipe/trim/petrify/update/zar): the output must not be the input itself.
/// </summary>
public static string? CheckSingleInputOutput(string input, string? outputName)
{
try
{
if (string.IsNullOrWhiteSpace(outputName))
return null;
if (XisoPaths.AreSamePath(input, outputName))
{
return $"Error: -o output {outputName} is the same file as the input {input};" +
" choose another name\n";
}
return null;
}
catch (Exception ex)
{
Log.Error(ex, "CheckSingleInputOutput failed for {Input}", input);
BugReporter.ReportException(ex, $"CheckSingleInputOutput failed for {input}");
// CLI-024: fail closed (see CheckRewriteOutput).
return
$"Error: could not verify -o output {outputName ?? "<none>"} against {input} ({ex.Message}); refusing to overwrite\n";
}
}
/// <summary>
/// Rebuild output must not clobber any component (xiso, video, filler/seed,
/// update) nor the sectors file it is read from while writing.
/// </summary>
public static string? CheckRebuildOutput(string output, string? securitySectorsPath,
params string?[] parts)
{
try
{
foreach (string? part in parts)
{
if (!string.IsNullOrWhiteSpace(part) && XisoPaths.AreSamePath(part, output))
{
return $"Error: rebuild output {output} is the same file as input {part};" +
" choose another name\n";
}
}
if (!string.IsNullOrWhiteSpace(securitySectorsPath) &&
XisoPaths.AreSamePath(securitySectorsPath, output))
{
return $"Error: rebuild output {output} is the same file as the sectors file {securitySectorsPath};" +
" choose another name\n";
}
return null;
}
catch (Exception ex)
{
Log.Error(ex, "CheckRebuildOutput failed for {Output}", output);
BugReporter.ReportException(ex, $"CheckRebuildOutput failed for {output}");
// CLI-024: fail closed (see CheckRewriteOutput).
return
$"Error: could not verify rebuild output {output} against its inputs ({ex.Message}); refusing to overwrite\n";
}
}
/// <summary>
/// Compress/decompress output (explicit or derived default) must not be the
/// source file itself. Split-part collisions beyond the base name are caught
/// by the library backstop (<see cref="CisoWriter.CompressToCso"/>).
/// </summary>
public static string? CheckImageOutput(string source, string output)
{
try
{
if (XisoPaths.AreSamePath(source, output))
{
return $"Error: output {output} is the same file as the input {source};" +
" choose another name\n";
}
return null;
}
catch (Exception ex)
{
Log.Error(ex, "CheckImageOutput failed for {Source}", source);
BugReporter.ReportException(ex, $"CheckImageOutput failed for {source}");
// CLI-024: fail closed (see CheckRewriteOutput).
return $"Error: could not verify output {output} against {source} ({ex.Message}); refusing to overwrite\n";
}
}
}