@@ -299,38 +299,43 @@ impl ControlResponse { |
| 299 | .ok_or(ParseError::InvalidField(part.to_string()))?; | 299 | .ok_or(ParseError::InvalidField(part.to_string()))?; |
| 300 | match key { | 300 | match key { |
| 301 | "protocol" => { | 301 | "protocol" => { |
| 302 | - protocol_version = Some( | 302 | + let parsed = value |
| 303 | - value | | |
| 304 | .parse::<u16>() | 303 | .parse::<u16>() |
| 305 | - .map_err(|_| ParseError::InvalidField(part.to_string()))?, | 304 | + .map_err(|_| ParseError::InvalidField(part.to_string()))?; |
| 306 | - ); | 305 | + if protocol_version.replace(parsed).is_some() { |
| | 306 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 307 | + } |
| 307 | } | 308 | } |
| 308 | "health" => { | 309 | "health" => { |
| 309 | - health = HealthStatus::parse(value); | 310 | + let parsed = HealthStatus::parse(value) |
| 310 | - if health.is_none() { | 311 | + .ok_or(ParseError::InvalidField(part.to_string()))?; |
| | 312 | + if health.replace(parsed).is_some() { |
| 311 | return Err(ParseError::InvalidField(part.to_string())); | 313 | return Err(ParseError::InvalidField(part.to_string())); |
| 312 | } | 314 | } |
| 313 | } | 315 | } |
| 314 | "in_flight" => { | 316 | "in_flight" => { |
| 315 | - in_flight_requests = Some( | 317 | + let parsed = value |
| 316 | - value | | |
| 317 | .parse::<usize>() | 318 | .parse::<usize>() |
| 318 | - .map_err(|_| ParseError::InvalidField(part.to_string()))?, | 319 | + .map_err(|_| ParseError::InvalidField(part.to_string()))?; |
| 319 | - ); | 320 | + if in_flight_requests.replace(parsed).is_some() { |
| | 321 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 322 | + } |
| 320 | } | 323 | } |
| 321 | "total" => { | 324 | "total" => { |
| 322 | - total_requests = Some( | 325 | + let parsed = value |
| 323 | - value | | |
| 324 | .parse::<usize>() | 326 | .parse::<usize>() |
| 325 | - .map_err(|_| ParseError::InvalidField(part.to_string()))?, | 327 | + .map_err(|_| ParseError::InvalidField(part.to_string()))?; |
| 326 | - ); | 328 | + if total_requests.replace(parsed).is_some() { |
| | 329 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 330 | + } |
| 327 | } | 331 | } |
| 328 | "terminal" => { | 332 | "terminal" => { |
| 329 | - terminal_requests = Some( | 333 | + let parsed = value |
| 330 | - value | | |
| 331 | .parse::<usize>() | 334 | .parse::<usize>() |
| 332 | - .map_err(|_| ParseError::InvalidField(part.to_string()))?, | 335 | + .map_err(|_| ParseError::InvalidField(part.to_string()))?; |
| 333 | - ); | 336 | + if terminal_requests.replace(parsed).is_some() { |
| | 337 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 338 | + } |
| 334 | } | 339 | } |
| 335 | _ => return Err(ParseError::InvalidField(part.to_string())), | 340 | _ => return Err(ParseError::InvalidField(part.to_string())), |
| 336 | } | 341 | } |
@@ -358,8 +363,16 @@ impl ControlResponse { |
| 358 | .split_once('=') | 363 | .split_once('=') |
| 359 | .ok_or(ParseError::InvalidField(part.to_string()))?; | 364 | .ok_or(ParseError::InvalidField(part.to_string()))?; |
| 360 | match key { | 365 | match key { |
| 361 | - "id" => id = Some(value.to_string()), | 366 | + "id" => { |
| 362 | - "state" => state = Some(value.to_string()), | 367 | + if id.replace(value.to_string()).is_some() { |
| | 368 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 369 | + } |
| | 370 | + } |
| | 371 | + "state" => { |
| | 372 | + if state.replace(value.to_string()).is_some() { |
| | 373 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 374 | + } |
| | 375 | + } |
| 363 | _ => return Err(ParseError::InvalidField(part.to_string())), | 376 | _ => return Err(ParseError::InvalidField(part.to_string())), |
| 364 | } | 377 | } |
| 365 | } | 378 | } |
@@ -379,15 +392,18 @@ impl ControlResponse { |
| 379 | .ok_or(ParseError::InvalidField(part.to_string()))?; | 392 | .ok_or(ParseError::InvalidField(part.to_string()))?; |
| 380 | match key { | 393 | match key { |
| 381 | "ids" => { | 394 | "ids" => { |
| 382 | - if value == "-" { | 395 | + let parsed = if value == "-" { |
| 383 | - ids = Some(Vec::new()); | 396 | + Vec::new() |
| 384 | } else { | 397 | } else { |
| 385 | let parsed = | 398 | let parsed = |
| 386 | value.split(',').map(str::to_string).collect::<Vec<_>>(); | 399 | value.split(',').map(str::to_string).collect::<Vec<_>>(); |
| 387 | if parsed.iter().any(|id| id.is_empty()) { | 400 | if parsed.iter().any(|id| id.is_empty()) { |
| 388 | return Err(ParseError::InvalidField(part.to_string())); | 401 | return Err(ParseError::InvalidField(part.to_string())); |
| 389 | } | 402 | } |
| 390 | - ids = Some(parsed); | 403 | + parsed |
| | 404 | + }; |
| | 405 | + if ids.replace(parsed).is_some() { |
| | 406 | + return Err(ParseError::InvalidField(part.to_string())); |
| 391 | } | 407 | } |
| 392 | } | 408 | } |
| 393 | _ => return Err(ParseError::InvalidField(part.to_string())), | 409 | _ => return Err(ParseError::InvalidField(part.to_string())), |
@@ -403,24 +419,50 @@ impl ControlResponse { |
| 403 | let mut sender = None; | 419 | let mut sender = None; |
| 404 | let mut app_id = None; | 420 | let mut app_id = None; |
| 405 | let mut parent_window = None; | 421 | let mut parent_window = None; |
| | 422 | + let mut saw_app_id = false; |
| | 423 | + let mut saw_parent_window = false; |
| 406 | | 424 | |
| 407 | for part in parts { | 425 | for part in parts { |
| 408 | let (key, value) = part | 426 | let (key, value) = part |
| 409 | .split_once('=') | 427 | .split_once('=') |
| 410 | .ok_or(ParseError::InvalidField(part.to_string()))?; | 428 | .ok_or(ParseError::InvalidField(part.to_string()))?; |
| 411 | match key { | 429 | match key { |
| 412 | - "id" => id = Some(value.to_string()), | 430 | + "id" => { |
| 413 | - "state" => state = Some(value.to_string()), | 431 | + if id.replace(value.to_string()).is_some() { |
| 414 | - "sender" => sender = Some(value.to_string()), | 432 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 433 | + } |
| | 434 | + } |
| | 435 | + "state" => { |
| | 436 | + if state.replace(value.to_string()).is_some() { |
| | 437 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 438 | + } |
| | 439 | + } |
| | 440 | + "sender" => { |
| | 441 | + if sender.replace(value.to_string()).is_some() { |
| | 442 | + return Err(ParseError::InvalidField(part.to_string())); |
| | 443 | + } |
| | 444 | + } |
| 415 | "app_id" => { | 445 | "app_id" => { |
| 416 | - if value != "-" { | 446 | + if saw_app_id { |
| 417 | - app_id = Some(value.to_string()); | 447 | + return Err(ParseError::InvalidField(part.to_string())); |
| 418 | } | 448 | } |
| | 449 | + saw_app_id = true; |
| | 450 | + app_id = if value == "-" { |
| | 451 | + None |
| | 452 | + } else { |
| | 453 | + Some(value.to_string()) |
| | 454 | + }; |
| 419 | } | 455 | } |
| 420 | "parent" => { | 456 | "parent" => { |
| 421 | - if value != "-" { | 457 | + if saw_parent_window { |
| 422 | - parent_window = Some(value.to_string()); | 458 | + return Err(ParseError::InvalidField(part.to_string())); |
| 423 | } | 459 | } |
| | 460 | + saw_parent_window = true; |
| | 461 | + parent_window = if value == "-" { |
| | 462 | + None |
| | 463 | + } else { |
| | 464 | + Some(value.to_string()) |
| | 465 | + }; |
| 424 | } | 466 | } |
| 425 | _ => return Err(ParseError::InvalidField(part.to_string())), | 467 | _ => return Err(ParseError::InvalidField(part.to_string())), |
| 426 | } | 468 | } |
@@ -447,12 +489,18 @@ impl ControlResponse { |
| 447 | .ok_or(ParseError::InvalidField(field.to_string()))?; | 489 | .ok_or(ParseError::InvalidField(field.to_string()))?; |
| 448 | match key { | 490 | match key { |
| 449 | "code" => { | 491 | "code" => { |
| 450 | - code = | 492 | + let parsed = value |
| 451 | - Some(value.parse::<u32>().map_err(|_| { | 493 | + .parse::<u32>() |
| 452 | - ParseError::InvalidField(field.to_string()) | 494 | + .map_err(|_| ParseError::InvalidField(field.to_string()))?; |
| 453 | - })?); | 495 | + if code.replace(parsed).is_some() { |
| | 496 | + return Err(ParseError::InvalidField(field.to_string())); |
| | 497 | + } |
| | 498 | + } |
| | 499 | + "reason" => { |
| | 500 | + if reason.replace(value.to_string()).is_some() { |
| | 501 | + return Err(ParseError::InvalidField(field.to_string())); |
| | 502 | + } |
| 454 | } | 503 | } |
| 455 | - "reason" => reason = Some(value.to_string()), | | |
| 456 | _ => return Err(ParseError::InvalidField(field.to_string())), | 504 | _ => return Err(ParseError::InvalidField(field.to_string())), |
| 457 | } | 505 | } |
| 458 | } | 506 | } |
@@ -619,4 +667,25 @@ mod tests { |
| 619 | let parsed = ControlRequest::parse_line("inspect id=req-1 bogus=1"); | 667 | let parsed = ControlRequest::parse_line("inspect id=req-1 bogus=1"); |
| 620 | assert_eq!(parsed, None); | 668 | assert_eq!(parsed, None); |
| 621 | } | 669 | } |
| | 670 | + |
| | 671 | + #[test] |
| | 672 | + fn response_parse_rejects_duplicate_fields() { |
| | 673 | + assert!( |
| | 674 | + ControlResponse::parse_line( |
| | 675 | + "status protocol=1 protocol=2 health=healthy in_flight=0 total=0 terminal=0", |
| | 676 | + ) |
| | 677 | + .is_err() |
| | 678 | + ); |
| | 679 | + assert!( |
| | 680 | + ControlResponse::parse_line("ack request id=req-1 id=req-2 state=pending").is_err() |
| | 681 | + ); |
| | 682 | + assert!(ControlResponse::parse_line("list ids=req-1 ids=req-2").is_err()); |
| | 683 | + assert!( |
| | 684 | + ControlResponse::parse_line( |
| | 685 | + "snapshot id=req-1 state=pending sender=:1.2 app_id=- app_id=org.test.App parent=-", |
| | 686 | + ) |
| | 687 | + .is_err() |
| | 688 | + ); |
| | 689 | + assert!(ControlResponse::parse_line("error code=2 reason=bad reason=worse").is_err()); |
| | 690 | + } |
| 622 | } | 691 | } |