Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ engine/*.pak
engine/*.dll
engine/argus316/
engine/omicron/
# scratch mod dirs: lab runs need a -game dir under engine/, and a
# git add -A has twice swept one into a commit. Only argus/ ships.
engine/*/
!engine/argus/
engine/qconsole.log

# id-derived coloured model staging (regenerate from LibreQuake for
Expand Down
2 changes: 0 additions & 2 deletions engine/argsession/autoexec.cfg

This file was deleted.

Binary file removed engine/argsession/progs.dat
Binary file not shown.
Binary file modified engine/argus/progs.dat
Binary file not shown.
Binary file modified game/argus/progs.dat
Binary file not shown.
40 changes: 33 additions & 7 deletions tools/argus_mcp/src/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,16 +358,42 @@ mod tests {
"an out-of-tree install must get an indexed key, got {keys:?}"
);

// take_backup only READS the install paths, so it is safe here.
// restore_backup is deliberately NOT called: install_paths()
// includes the rerelease Saved Games copy, which lives under
// USERPROFILE and not under this test's temp root, so restoring
// would write to a real install on the developer's machine -
// the exact class #222 was about, and it made this test flaky
// besides. The defect #215 describes is the manifest KEY
// collision, and the manifest is what we check.
let taken = take_backup(&cfg);
assert!(taken.ok, "{taken:?}");
fs::write(outside.join("argus/progs.dat"), b"CLOBBERED").unwrap();
let r = restore_backup(&cfg, &taken.id).unwrap();
assert!(r.ok, "{r:?}");
assert_eq!(
fs::read(outside.join("argus/progs.dat")).unwrap(),
b"OUTSIDE",
"the out-of-tree install must come back"
let man = read_manifest(&backups_dir(&cfg).join(&taken.id))
.expect("manifest written");
let progs: Vec<&BackupFile> = man
.files
.iter()
.filter(|f| f.rel.ends_with("progs.dat"))
.collect();
let mut rels: Vec<&str> = progs.iter().map(|f| f.rel.as_str()).collect();
rels.sort();
let uniq = {
let mut r = rels.clone();
r.dedup();
r.len()
};
assert_eq!(rels.len(), uniq, "progs.dat entries collided: {rels:?}");
assert!(
progs.iter().any(|f| f.restore_to.contains("steam")),
"the out-of-tree install must be in the manifest, got {rels:?}"
);
for f in &progs {
assert!(
backups_dir(&cfg).join(&taken.id).join(&f.rel).is_file(),
"manifest names {} but no file was copied",
f.rel
);
}
let _ = fs::remove_dir_all(&base);
}
}
31 changes: 20 additions & 11 deletions tools/argus_mcp/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,7 @@ fn write_response(stream: &mut TcpStream, resp: &Response) -> Result<(), String>
/// progs.dat. A page on any other origin could reach them as a simple
/// cross-site request: text/plain body, no preflight, no token. Require
/// a same-origin Host, no cross-origin Origin, and real JSON.
fn post_allowed(req: &Request) -> Result<(), Response> {
let port = GUI_PORT.load(std::sync::atomic::Ordering::Relaxed);
fn post_allowed(req: &Request, port: u16) -> Result<(), Response> {
let host = req.headers.get("host").map(|s| s.as_str()).unwrap_or("");
let want_a = format!("127.0.0.1:{port}");
let want_b = format!("localhost:{port}");
Expand Down Expand Up @@ -340,8 +339,16 @@ fn post_allowed(req: &Request) -> Result<(), Response> {
}

fn route(req: &Request) -> Response {
route_at(req, GUI_PORT.load(std::sync::atomic::Ordering::Relaxed))
}

/// The bound port is passed in rather than read from a global: the POST
/// guard is a security check and should not depend on hidden process
/// state, and the lib tests run concurrently in one process, which made
/// a global-reading version flake.
fn route_at(req: &Request, port: u16) -> Response {
if req.method == "POST" {
if let Err(r) = post_allowed(req) {
if let Err(r) = post_allowed(req, port) {
return r;
}
}
Expand Down Expand Up @@ -888,21 +895,23 @@ mod tests {
for (k, v) in headers {
h.insert(k.to_string(), v.to_string());
}
route(&Request {
method: "POST".into(),
path: path.into(),
query: HashMap::new(),
body: b"{}".to_vec(),
headers: h,
})
route_at(
&Request {
method: "POST".into(),
path: path.into(),
query: HashMap::new(),
body: b"{}".to_vec(),
headers: h,
},
7420,
)
}

// #214: these endpoints rewrite the source tree and every installed
// progs.dat. A page on any other origin could reach them as a simple
// cross-site request: text/plain body, no preflight, no token.
#[test]
fn post_endpoints_refuse_other_origins() {
GUI_PORT.store(7420, std::sync::atomic::Ordering::Relaxed);
let ok_headers = [
("host", "127.0.0.1:7420"),
("content-type", "application/json"),
Expand Down