From 08fdb00de0ac8ce74d03a249eb53042d458ed6f4 Mon Sep 17 00:00:00 2001 From: "Teppei.F" <37261985+T3pp31@users.noreply.github.com> Date: Sat, 30 May 2026 09:33:17 +0900 Subject: [PATCH 1/3] feat: add bounded CLI input reading Introduce bounded stdin/file reads to prevent memory exhaustion from untrusted input and document per-path size limits and API modes. Co-authored-by: Cursor --- README.md | 27 +++++ src/bounded_input.rs | 196 +++++++++++++++++++++++++++++++++++ src/caesar_cipher.rs | 12 ++- src/cli.rs | 55 +++++----- src/config.rs | 18 ++++ src/lib.rs | 12 +++ tests/bounded_input_tests.rs | 89 ++++++++++++++++ tests/config_tests.rs | 62 ++--------- 8 files changed, 386 insertions(+), 85 deletions(-) create mode 100644 src/bounded_input.rs create mode 100644 tests/bounded_input_tests.rs diff --git a/README.md b/README.md index c1328c5..ea27451 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,15 @@ fn main() { } ``` +### Basic vs Safe APIs + +| API | Shift range | Empty / whitespace-only text | +|-----|-------------|------------------------------| +| `encrypt` / `decrypt` | Any `i16` (normalized mod 26) | Allowed | +| `encrypt_safe` / `decrypt_safe` | -25 to 25 only | Returns `CipherError::EmptyText` | + +Use `*_safe` when you want validation errors instead of silent normalization. + ### Safe Functions with Error Handling ```rust @@ -128,6 +137,24 @@ caesar_cipher_enc_dec decrypt --file encrypted.txt --shift 5 --output decrypted. caesar_cipher_enc_dec encrypt --text "Hello" --shift 3 --safe ``` +Without `--safe`, the CLI accepts any `i16` shift (values are normalized modulo 26) and allows empty input. With `--safe`, shift must be in -25..=25 and text must not be empty or whitespace-only. + +### Input limits and file requirements + +Maximum payload size is **10 MB** (`MAX_INPUT_SIZE` in `config`). How the cap is applied depends on the input path: + +| Input path | What is limited | Notes | +|------------|-----------------|-------| +| `--text` | String length | Exactly 10 MB is allowed (`len > MAX` is rejected). | +| `--file` | Entire file size | Exactly 10 MB files are allowed. Must be a **regular file** (not a directory, device, or FIFO). Read uses a streaming byte cap. | +| Stdin / interactive line | Line **content** before `\n` | Up to 10 MB of content per line; `\n` is not counted toward the cap (buffer may be up to 10 MB + 1 byte). EOF without newline also allows exactly 10 MB. | + +Additional notes: + +- Shift prompts in interactive mode are capped at **64 bytes** per line (`MAX_SHIFT_LINE_SIZE`). +- Stdin uses chunked reads (8 KB buffer) for performance; the 10 MB cap still applies to line content. +- If you raise `MAX_INPUT_SIZE` in the future, consider tuning the read buffer in `bounded_input.rs`. + ## Supported Characters - **Uppercase letters**: A-Z diff --git a/src/bounded_input.rs b/src/bounded_input.rs new file mode 100644 index 0000000..0de4880 --- /dev/null +++ b/src/bounded_input.rs @@ -0,0 +1,196 @@ +//! Bounded input reading for CLI and interactive mode. +//! +//! Reads stdin lines and files with a hard byte limit so untrusted input +//! cannot exhaust memory before validation runs. + +use std::fs::File; +use std::io::{self, Read}; +use std::path::Path; + +/// Chunk size for bounded line reads (not the input cap; keeps I/O efficient up to `max_bytes`). +const READ_CHUNK_SIZE: usize = 8192; + +/// Returns a consistent error message when input exceeds `max_bytes`. +pub fn input_size_exceeded_message(max_bytes: usize) -> String { + format!("Input exceeds maximum size of {} bytes", max_bytes) +} + +/// Reads a single line from `reader`, stopping at newline or EOF. +/// +/// At most `max_bytes` bytes of **line content** may appear before the newline. +/// The trailing `\n` (if present) is stored but does not count toward `max_bytes`. +/// If a non-newline byte would push content past `max_bytes`, returns an error. +pub fn read_line_bounded(reader: &mut R, max_bytes: usize) -> io::Result { + let mut line = Vec::with_capacity(max_bytes.min(READ_CHUNK_SIZE)); + let mut buf = [0u8; READ_CHUNK_SIZE]; + + loop { + let n = reader.read(&mut buf)?; + if n == 0 { + break; + } + for &byte in &buf[..n] { + if byte == b'\n' { + line.push(byte); + return String::from_utf8(line) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)); + } + if line.len() >= max_bytes { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + input_size_exceeded_message(max_bytes), + )); + } + line.push(byte); + } + } + + String::from_utf8(line).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) +} + +/// Reads a regular file up to `max_bytes`, with streaming size enforcement. +/// +/// Rejects non-regular files (directories, devices, FIFOs, etc.) and stops +/// reading once more than `max_bytes` have been read (TOCTOU-safe). +pub fn read_file_bounded(path: impl AsRef, max_bytes: usize) -> io::Result { + let path = path.as_ref(); + let path_display = path.display().to_string(); + + let metadata = std::fs::metadata(path).map_err(|e| { + io::Error::new( + io::ErrorKind::NotFound, + format!("Failed to read file '{}': {}", path_display, e), + ) + })?; + + if !metadata.is_file() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Input path '{}' is not a regular file", path_display), + )); + } + + if metadata.len() > max_bytes as u64 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "Input file '{}' exceeds maximum size of {} bytes", + path_display, max_bytes + ), + )); + } + + let file = File::open(path).map_err(|e| { + io::Error::new( + io::ErrorKind::NotFound, + format!("Failed to read file '{}': {}", path_display, e), + ) + })?; + + let mut limited = file.take(max_bytes as u64 + 1); + let mut bytes = Vec::new(); + limited.read_to_end(&mut bytes)?; + + if bytes.len() > max_bytes { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "Input file '{}' exceeds maximum size of {} bytes", + path_display, max_bytes + ), + )); + } + + String::from_utf8(bytes).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Input file '{}' is not valid UTF-8: {}", path_display, e), + ) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn read_line_bounded_accepts_line_within_limit() { + // Given: A line shorter than the limit + let mut reader = Cursor::new(b"hello\n"); + + // When: Reading with a generous limit + let result = read_line_bounded(&mut reader, 10); + + // Then: Returns content including newline + assert_eq!(result.unwrap(), "hello\n"); + } + + #[test] + fn read_line_bounded_rejects_oversized_line() { + // Given: A line longer than the limit (no early stop on allocation) + let mut reader = Cursor::new(b"abcdef\n"); + + // When: Limit is 3 bytes + let result = read_line_bounded(&mut reader, 3); + + // Then: Fails with size error + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("exceeds maximum size")); + } + + #[test] + fn read_line_bounded_eof_without_newline() { + // Given: Input without trailing newline + let mut reader = Cursor::new(b"hi"); + + // When: Reading until EOF + let result = read_line_bounded(&mut reader, 10); + + // Then: Returns bytes read + assert_eq!(result.unwrap(), "hi"); + } + + #[test] + fn read_line_bounded_accepts_exactly_max_bytes_at_eof() { + // Given: Exactly max_bytes of content with no trailing newline + let payload = vec![b'x'; 5]; + let mut reader = Cursor::new(payload); + + // When: Limit equals payload length + let result = read_line_bounded(&mut reader, 5); + + // Then: Succeeds (matches --text / file cap semantics for payload size) + assert_eq!(result.unwrap(), "xxxxx"); + } + + #[test] + fn read_line_bounded_allows_content_plus_newline_at_limit() { + // Given: max_bytes of content followed by newline (newline not counted toward cap) + let mut payload = vec![b'a'; 3]; + payload.push(b'\n'); + let mut reader = Cursor::new(payload); + + // When: Limit equals content length + let result = read_line_bounded(&mut reader, 3); + + // Then: Succeeds; newline is stored but not counted toward max_bytes + assert_eq!(result.unwrap(), "aaa\n"); + } + + #[test] + fn read_line_bounded_rejects_one_byte_over_content_limit() { + // Given: Content one byte over the limit, no newline + let payload = vec![b'b'; 4]; + let mut reader = Cursor::new(payload); + + // When: Limit is 3 + let result = read_line_bounded(&mut reader, 3); + + // Then: Fails on the fourth byte + assert!(result.is_err()); + } +} diff --git a/src/caesar_cipher.rs b/src/caesar_cipher.rs index 4524354..05e2c40 100644 --- a/src/caesar_cipher.rs +++ b/src/caesar_cipher.rs @@ -3,6 +3,13 @@ //! Provides easy-to-use Caesar cipher encryption and decryption. //! Set text and shift number to encrypt or decrypt. //! +//! ## Basic vs safe APIs +//! +//! | Function | Shift | Empty / whitespace-only text | +//! |----------|-------|--------------------------------| +//! | [`encrypt`] / [`decrypt`] | Any `i16` (normalized mod 26) | Allowed | +//! | [`encrypt_safe`] / [`decrypt_safe`] | -25 to 25 only | Returns [`CipherError::EmptyText`] | +//! //! # Usage //! //! ``` @@ -185,10 +192,7 @@ pub fn encrypt_safe(text: &str, shift: i16) -> Result { /// ``` pub fn decrypt_safe(text: &str, shift: i16) -> Result { validate_safe_inputs(text, shift)?; - - let negated = -(shift as i32); - let normalized = negated.rem_euclid(ALPHABET_SIZE as i32) as i16; - Ok(shift_text(text, normalized)) + Ok(decrypt(text, shift)) } /// Validates shared inputs for safe Caesar cipher APIs. diff --git a/src/cli.rs b/src/cli.rs index d15968f..6b5956a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,8 +2,12 @@ use clap::{Args, Parser, Subcommand}; use std::fs; use std::io::{self, Write}; +use crate::bounded_input::{read_file_bounded, read_line_bounded}; use crate::caesar_cipher::{decrypt, decrypt_safe, encrypt, encrypt_safe}; -use crate::config::{DEFAULT_SHIFT, MAX_BRUTE_FORCE_SHIFT, MAX_INPUT_SIZE, MAX_SHIFT, MIN_SHIFT}; +use crate::config::{ + DEFAULT_SHIFT, MAX_BRUTE_FORCE_SHIFT, MAX_INPUT_SIZE, MAX_SHIFT, MAX_SHIFT_LINE_SIZE, + MIN_SHIFT, +}; /// Main CLI structure for the Caesar cipher application /// @@ -154,30 +158,15 @@ fn get_input_text( } if let Some(f) = file { - let metadata = - fs::metadata(&f).map_err(|e| format!("Failed to read file '{}': {}", f, e))?; - if metadata.len() > MAX_INPUT_SIZE as u64 { - return Err(format!( - "Input file '{}' exceeds maximum size of {} bytes", - f, MAX_INPUT_SIZE - ) - .into()); - } - return fs::read_to_string(&f) - .map_err(|e| format!("Failed to read file '{}': {}", f, e).into()); + let input = read_file_bounded(&f, MAX_INPUT_SIZE) + .map_err(|e| e.to_string())?; + return Ok(trim_trailing_newline(&input).to_string()); } print!("Enter text: "); io::stdout().flush()?; - let mut input = String::new(); - io::stdin().read_line(&mut input)?; - if input.len() > MAX_INPUT_SIZE { - return Err(format!( - "Input text exceeds maximum size of {} bytes", - MAX_INPUT_SIZE - ) - .into()); - } + let mut stdin = io::stdin().lock(); + let input = read_line_bounded(&mut stdin, MAX_INPUT_SIZE).map_err(|e| e.to_string())?; Ok(trim_trailing_newline(&input).to_string()) } @@ -231,8 +220,8 @@ fn output_result( fn prompt_for_text(prompt: &str) -> io::Result { print!("{}", prompt); io::stdout().flush()?; - let mut input = String::new(); - io::stdin().read_line(&mut input)?; + let mut stdin = io::stdin().lock(); + let input = read_line_bounded(&mut stdin, MAX_INPUT_SIZE)?; Ok(trim_trailing_newline(&input).to_string()) } @@ -279,8 +268,8 @@ pub(crate) fn validate_shift_input(input: &str) -> (i16, Option) { fn prompt_for_shift() -> io::Result { print!("Enter shift value (default: {}): ", DEFAULT_SHIFT); io::stdout().flush()?; - let mut shift_str = String::new(); - io::stdin().read_line(&mut shift_str)?; + let mut stdin = io::stdin().lock(); + let shift_str = read_line_bounded(&mut stdin, MAX_SHIFT_LINE_SIZE)?; let (shift, warning) = validate_shift_input(&shift_str); if let Some(msg) = warning { @@ -417,6 +406,22 @@ mod tests { // Input size limit tests // ------------------------------------------------------------------------- + #[test] + fn test_read_file_bounded_rejects_directory() { + // Given: A directory path + let dir = tempfile::tempdir().unwrap(); + + // When: Reading as a bounded file + let result = crate::bounded_input::read_file_bounded(dir.path(), MAX_INPUT_SIZE); + + // Then: Rejects non-regular file + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("not a regular file")); + } + #[test] fn test_get_input_text_oversized_file_error() { // Given: A file that exceeds MAX_INPUT_SIZE diff --git a/src/config.rs b/src/config.rs index 90f7849..0c4acc5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,3 +26,21 @@ pub const DEFAULT_SHIFT: i16 = 3; /// Maximum input size in bytes (10 MB) pub const MAX_INPUT_SIZE: usize = 10 * 1024 * 1024; + +/// Maximum bytes for a single interactive shift prompt line +pub const MAX_SHIFT_LINE_SIZE: usize = 64; + +// Compile-time checks for configuration relationships (see also tests/config_tests.rs). +const _: () = { + assert!(UPPERCASE_BASE < LOWERCASE_BASE); + assert!(MAX_SHIFT < ALPHABET_SIZE); + assert!(DEFAULT_SHIFT >= 1 && DEFAULT_SHIFT <= MAX_SHIFT); + assert!(ALPHABET_SIZE > 0); + assert!(MAX_SHIFT > 0); + assert!(UPPERCASE_BASE > 0); + assert!(LOWERCASE_BASE > 0); + assert!(MAX_BRUTE_FORCE_SHIFT > 0); + assert!(DEFAULT_SHIFT > 0); + assert!(UPPERCASE_BASE >= 0 && UPPERCASE_BASE <= 127); + assert!(LOWERCASE_BASE >= 0 && LOWERCASE_BASE <= 127); +}; diff --git a/src/lib.rs b/src/lib.rs index 59679a1..1b006fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,18 @@ //! - [`config`] - Centralized constants and configuration //! - [`caesar_cipher`] - Core encryption/decryption functionality //! - [`cli`] - Command-line interface implementation +//! +//! ## API modes (library) +//! +//! | API | Shift range | Empty / whitespace-only text | +//! |-----|-------------|------------------------------| +//! | [`caesar_cipher::encrypt`] / [`caesar_cipher::decrypt`] | Any `i16` (normalized mod 26) | Allowed | +//! | [`caesar_cipher::encrypt_safe`] / [`caesar_cipher::decrypt_safe`] | -25 to 25 only | `CipherError::EmptyText` | +//! +//! Use the `*_safe` functions when you want validation errors instead of silent normalization. + +/// Bounded stdin/file reading used by the CLI +mod bounded_input; /// Centralized configuration and constants pub mod config; diff --git a/tests/bounded_input_tests.rs b/tests/bounded_input_tests.rs new file mode 100644 index 0000000..a4c6906 --- /dev/null +++ b/tests/bounded_input_tests.rs @@ -0,0 +1,89 @@ +//! Integration tests for bounded file input and CLI rejection paths. + +use caesar_cipher_enc_dec::config::MAX_INPUT_SIZE; +use std::io::Write; +use tempfile::{NamedTempFile, TempDir}; + +#[test] +fn cli_rejects_directory_as_input_file() { + // Given: A directory path + let dir = TempDir::new().unwrap(); + let path = dir.path().to_string_lossy(); + + // When: Encrypt with --file pointing at the directory + let output = std::process::Command::new(env!("CARGO_BIN_EXE_caesar_cipher_enc_dec")) + .args(["encrypt", "--file", &path, "--shift", "3"]) + .output() + .expect("failed to run binary"); + + // Then: Fails with a clear message + assert!(!output.status.success()); + let combined = format!( + "{}{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ); + assert!( + combined.contains("not a regular file"), + "expected directory rejection, got: {}", + combined + ); +} + +#[test] +fn cli_accepts_file_at_max_input_size() { + // Given: A file exactly at MAX_INPUT_SIZE + let mut temp_file = NamedTempFile::new().unwrap(); + temp_file.write_all(&vec![b'A'; MAX_INPUT_SIZE]).unwrap(); + temp_file.flush().unwrap(); + + // When: Encrypt from file + let output = std::process::Command::new(env!("CARGO_BIN_EXE_caesar_cipher_enc_dec")) + .args([ + "encrypt", + "--file", + temp_file.path().to_str().unwrap(), + "--shift", + "1", + ]) + .output() + .expect("failed to run binary"); + + // Then: Succeeds + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + +#[test] +fn cli_rejects_file_over_max_input_size() { + // Given: A file one byte over the limit + let mut temp_file = NamedTempFile::new().unwrap(); + temp_file + .write_all(&vec![b'B'; MAX_INPUT_SIZE + 1]) + .unwrap(); + temp_file.flush().unwrap(); + + // When: Encrypt from file + let output = std::process::Command::new(env!("CARGO_BIN_EXE_caesar_cipher_enc_dec")) + .args([ + "encrypt", + "--file", + temp_file.path().to_str().unwrap(), + "--shift", + "1", + ]) + .output() + .expect("failed to run binary"); + + // Then: Fails with size message + assert!(!output.status.success()); + let combined = format!( + "{}{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ); + assert!(combined.contains("exceeds maximum size")); +} diff --git a/tests/config_tests.rs b/tests/config_tests.rs index a693079..d358a4b 100644 --- a/tests/config_tests.rs +++ b/tests/config_tests.rs @@ -89,30 +89,8 @@ fn test_alphabet_size_equals_max_shift_plus_one() { assert_eq!(ALPHABET_SIZE, MAX_SHIFT + 1); } -#[test] -fn test_uppercase_base_less_than_lowercase_base() { - // Given: UPPERCASE_BASE and LOWERCASE_BASE - // When: Comparing them - // Then: Uppercase base should be less than lowercase base - assert!(UPPERCASE_BASE < LOWERCASE_BASE); -} - -#[test] -fn test_max_shift_less_than_alphabet_size() { - // Given: MAX_SHIFT and ALPHABET_SIZE - // When: Comparing them - // Then: MAX_SHIFT should be strictly less than ALPHABET_SIZE - assert!(MAX_SHIFT < ALPHABET_SIZE); -} - -#[test] -fn test_default_shift_in_valid_range() { - // Given: DEFAULT_SHIFT and MAX_SHIFT - // When: Checking range - // Then: DEFAULT_SHIFT should be between 1 and MAX_SHIFT inclusive - assert!(DEFAULT_SHIFT >= 1); - assert!(DEFAULT_SHIFT <= MAX_SHIFT); -} +// Relationships among UPPERCASE_BASE, MAX_SHIFT, DEFAULT_SHIFT, etc. are +// checked at compile time in `src/config.rs` (`const _` block). #[test] fn test_max_brute_force_shift_equals_max_shift() { @@ -122,39 +100,11 @@ fn test_max_brute_force_shift_equals_max_shift() { assert_eq!(MAX_BRUTE_FORCE_SHIFT, MAX_SHIFT); } -// ============================================================================= -// Abnormal prevention (異常系防止) - 全定数が正の値であること -// ============================================================================= - -#[test] -fn test_all_constants_are_positive() { - // Given: All configuration constants - // When: Checking their signs - // Then: All should be positive - assert!(ALPHABET_SIZE > 0, "ALPHABET_SIZE must be positive"); - assert!(MAX_SHIFT > 0, "MAX_SHIFT must be positive"); - assert!(UPPERCASE_BASE > 0, "UPPERCASE_BASE must be positive"); - assert!(LOWERCASE_BASE > 0, "LOWERCASE_BASE must be positive"); - assert!( - MAX_BRUTE_FORCE_SHIFT > 0, - "MAX_BRUTE_FORCE_SHIFT must be positive" - ); - assert!(DEFAULT_SHIFT > 0, "DEFAULT_SHIFT must be positive"); -} - #[test] -fn test_base_values_are_valid_ascii() { - // Given: UPPERCASE_BASE and LOWERCASE_BASE - // When: Checking if they are valid ASCII letter ranges - // Then: They should be within valid u8 range and represent letters - assert!( - UPPERCASE_BASE >= 0 && UPPERCASE_BASE <= 127, - "UPPERCASE_BASE must be valid ASCII" - ); - assert!( - LOWERCASE_BASE >= 0 && LOWERCASE_BASE <= 127, - "LOWERCASE_BASE must be valid ASCII" - ); +fn test_base_values_are_valid_ascii_letters() { + // Given: UPPERCASE_BASE and LOWERCASE_BASE (sign/range checked at compile time in config.rs) + // When: Converting to characters + // Then: They represent 'A' and 'a' assert_eq!((UPPERCASE_BASE as u8) as char, 'A'); assert_eq!((LOWERCASE_BASE as u8) as char, 'a'); } From 5d126ee15b7f1a2514abf0db7e93a9dad9773f29 Mon Sep 17 00:00:00 2001 From: "Teppei.F" <37261985+T3pp31@users.noreply.github.com> Date: Sat, 30 May 2026 09:33:17 +0900 Subject: [PATCH 2/3] ci: run all-target clippy and rustsec audit-check Co-authored-by: Cursor --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8097346..7f7737d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,10 +31,15 @@ jobs: run: cargo test --verbose - name: Run clippy - run: cargo clippy -- -D warnings + run: cargo clippy --all-targets --all-features -- -D warnings - name: Check formatting run: cargo fmt --check + - name: Audit dependencies + uses: rustsec/audit-check@v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Build run: cargo build --release From 88f1518418d5fb7828b63971e42c3ba022b9fae2 Mon Sep 17 00:00:00 2001 From: "Teppei.F" <37261985+T3pp31@users.noreply.github.com> Date: Sat, 30 May 2026 09:45:08 +0900 Subject: [PATCH 3/3] style: apply rustfmt to cli.rs for CI cargo fmt --check failed on import layout and chained call formatting. Co-authored-by: Cursor --- src/cli.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 6b5956a..8517fef 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,8 +5,7 @@ use std::io::{self, Write}; use crate::bounded_input::{read_file_bounded, read_line_bounded}; use crate::caesar_cipher::{decrypt, decrypt_safe, encrypt, encrypt_safe}; use crate::config::{ - DEFAULT_SHIFT, MAX_BRUTE_FORCE_SHIFT, MAX_INPUT_SIZE, MAX_SHIFT, MAX_SHIFT_LINE_SIZE, - MIN_SHIFT, + DEFAULT_SHIFT, MAX_BRUTE_FORCE_SHIFT, MAX_INPUT_SIZE, MAX_SHIFT, MAX_SHIFT_LINE_SIZE, MIN_SHIFT, }; /// Main CLI structure for the Caesar cipher application @@ -158,8 +157,7 @@ fn get_input_text( } if let Some(f) = file { - let input = read_file_bounded(&f, MAX_INPUT_SIZE) - .map_err(|e| e.to_string())?; + let input = read_file_bounded(&f, MAX_INPUT_SIZE).map_err(|e| e.to_string())?; return Ok(trim_trailing_newline(&input).to_string()); }