an http based roguelike rpg about jelqing
Commit 177535ac8abf
tarball5 files changed+69-11
@@ -6,8 +6,8 @@ use serde::Deserialize;
66
use serde_json::{Value, json};77
88
use super::{9
- Floor, Grip, Outcome, Run, SHOP, SUSPICION_LIMIT, X_DIAGNOSIS, parse_credentials, round2,10
- tenths,9
+ Floor, Grip, Outcome, Run, SHOP, SUSPICION_LIMIT, X_DIAGNOSIS, honest, parse_credentials,10
+ round2,1111
};1212
use crate::problem::Problem;1313
@@ -114,6 +114,23 @@ impl Run {
114114
));115115
};116116
117
+ let spoken = authorization118
+ .split_once(' ')119
+ .map(|(_, rest)| rest.trim().trim_matches('"').to_ascii_lowercase())120
+ .unwrap_or_default();121
+ if TRUTHS.contains(&spoken.as_str()) {122
+ self.shame += 10;123
+ self.leave_door();124
+ return Ok(());125
+ }126
+ if parse_credentials(authorization, "basic").is_some() {127
+ return Err(challenge(Problem::new(128
+ StatusCode::UNAUTHORIZED,129
+ "basic",130
+ "Mom does not accept Basic authentication. Nobody should.",131
+ )));132
+ }133
+117134
let excuse = if digest {118135
let Some(params) = digest_params(authorization) else {119136
return Err(challenge(Problem::new(@@ -525,7 +542,7 @@ impl Run {
525542
self.touch();526543
return Err(exaggeration());527544
}528
- if tenths(claimed) != tenths(actual) {545
+ if !honest(claimed, actual) {529546
self.shame += 1;530547
self.touch();531548
return Err(@@ -625,7 +642,7 @@ impl Run {
625642
self.touch();626643
return Err(exaggeration());627644
}628
- if tenths(claimed) != tenths(actual) {645
+ if !honest(claimed, actual) {629646
self.shame += 1;630647
self.touch();631648
return Err(verification("Verification failed. The forum can tell."));@@ -672,9 +689,9 @@ impl Run {
672689
let Some(authorization) = authorization else {673690
return Err(intake.header(header::WWW_AUTHENTICATE, "Bearer realm=\"clinic\""));674691
};675
- let honest = parse_credentials(authorization, "bearer")692
+ let truthful = parse_credentials(authorization, "bearer")676693
.is_some_and(|token| token.eq_ignore_ascii_case("honesty"));677
- if !honest {694
+ if !truthful {678695
let hint = INTAKE[self.intake_attempts.min(INTAKE.len() - 1)];679696
self.intake_attempts += 1;680697
return Err(intake.header(@@ -339,6 +339,9 @@ impl Run {
339339
.header(header::CONNECTION, "close"));340340
}341341
if self.eq > 95 {342
+ if self.rng.random_ratio(1, 3) {343
+ return Err(self.rupture());344
+ }342345
self.shame += 5;343346
self.next_floor();344347
return Err(Problem::new(@@ -290,8 +290,8 @@ fn round3(x: f64) -> f64 {
290290
(x * 1000.0).round() / 1000.0291291
}292292
293
-fn tenths(x: f64) -> i64 {294
- (x * 10.0).round() as i64293
+fn honest(claimed: f64, actual: f64) -> bool {294
+ (claimed - actual).abs() < 0.06295295
}296296
297297
fn not_extended() -> Problem {@@ -190,6 +190,24 @@ fn unsupported(method: &Method, allow: &'static str) -> Response {
190190
method.as_str(),191191
"GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "TRACE" | "JELQ"192192
);193
+ if method.as_str() == "BREW" {194
+ return Problem::new(195
+ StatusCode::IM_A_TEAPOT,196
+ "brew",197
+ "The server is a teapot. RFC 2324 is clear on this. It is not yet short and stout.",198
+ )199
+ .into_response();200
+ }201
+ if method.as_str().eq_ignore_ascii_case("jelq") {202
+ return Problem::new(203
+ StatusCode::NOT_IMPLEMENTED,204
+ "not-implemented",205
+ format!(206
+ "The server does not implement {method}. Methods are case-sensitive. JELQ is shouted for a reason."207
+ ),208
+ )209
+ .into_response();210
+ }193211
if !known {194212
return Problem::new(195213
StatusCode::NOT_IMPLEMENTED,@@ -478,7 +496,7 @@ fn jelq(
478496
now: Instant,479497
) -> Response {480498
let techniques_href = format!("/runs/{}/techniques", run.id);481
- let Some(names) = query.get("technique") else {499
+ let Some(names) = query.get("technique").filter(|n| !n.trim().is_empty()) else {482500
return Problem::new(483501
StatusCode::BAD_REQUEST,484502
"technique-required",@@ -488,7 +506,7 @@ fn jelq(
488506
.into_response();489507
};490508
let mut techniques = Vec::new();491
- for name in names.split(',').map(str::trim) {509
+ for name in names.split(',').map(str::trim).filter(|n| !n.is_empty()) {492510
let Some(technique) = Technique::parse(name) else {493511
return Problem::new(494512
StatusCode::BAD_REQUEST,@@ -881,6 +899,12 @@ async fn grip(
881899
let headers = request.headers().clone();882900
let wanted = requested_grip(&headers);883901
let upgrade = wanted.map(|_| hyper::upgrade::on(&mut request));902
+ let detail = match header_str(&headers, header::UPGRADE) {903
+ Some(protocol) if wanted.is_none() => {904
+ format!("The server does not know the {protocol} grip. Grips are protocols.")905
+ }906
+ _ => "Grips are protocols. Change grip by upgrading the connection.".to_owned(),907
+ };884908
let response = with_run(&state, &id, &headers, Scope::Home, |run| {885909
match (method.as_str(), wanted) {886910
("GET" | "HEAD", Some(grip)) => {@@ -899,7 +923,7 @@ async fn grip(
899923
[(header::UPGRADE, "overhand, underhand")],900924
Json(json!({901925
"grip": run.grip.name(),902
- "detail": "Grips are protocols. Change grip by upgrading the connection.",926
+ "detail": detail,903927
})),904928
)905929
.into_response(),@@ -425,3 +425,17 @@ async fn http_1_0_gets_the_pdf_treatment() {
425425
let response = jelqd::app().oneshot(request).await.unwrap();426426
assert_eq!(response.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);427427
}428
+429
+#[tokio::test]430
+async fn the_displayed_length_is_accepted_as_honest() {431
+ let app = seeded_with(Floor::Forum, |run| run.length = 5.2496);432
+ let response = send(433
+ &app,434
+ "GET",435
+ "/runs/cafebabe/elder",436
+ &[("proxy-authorization", "Ruler 5.25")],437
+ "",438
+ )439
+ .await;440
+ assert_eq!(response.status(), StatusCode::OK);441
+}