rexor/
lib.rs

1//! This library provides simple file encryption and decryption using XOR logic.
2//! It allows you to encode or decode any file with a defined password,
3//! producing `.rxor` encrypted files.
4//!
5//! # Example: Encoding a file
6//! ```no_run
7//! use rexor::encode;
8//!
9//! fn main() -> std::io::Result<()> {
10//!     let input = "example.txt";
11//!     let password = "password123";
12//!
13//!     // Encode the file into "example.txt.rxor"
14//!     let output = encode(input, password, None)?;
15//!     println!("Encoded file saved at: {}", output);
16//!
17//!     Ok(())
18//! }
19//! ```
20//!
21//! # Example: Decoding a file
22//! ```no_run
23//! use rexor::decode;
24//!
25//! fn main() -> std::io::Result<()> {
26//!     let input = "example.txt.rxor";
27//!     let password = "password123";
28//!
29//!     // Decode the file back to its original form
30//!     let output = decode(input, password, None)?;
31//!     println!("Decoded file saved at: {}", output);
32//!
33//!     Ok(())
34//! }
35//! ```
36//!
37//! # Example: Custom output path
38//! ```no_run
39//! use rexor::{encode, decode};
40//!
41//! fn main() -> std::io::Result<()> {
42//!     let input = "example.txt";
43//!     let password = "password123";
44//!
45//!     // Encode to a custom location
46//!     let encoded = encode(input, password, Some("encrypted/output.rxor"))?;
47//!
48//!     // Decode back into another file
49//!     let decoded = decode(&encoded, password, Some("decrypted/example.txt"))?;
50//!
51//!     println!("Encoded: {}", encoded);
52//!     println!("Decoded: {}", decoded);
53//!
54//!     Ok(())
55//! }
56//! ```
57//!
58//! # Errors
59//! The functions return [`std::io::Result<String>`].
60//! Typical errors include:
61//! - Invalid input paths or missing files
62//! - Empty password ([`std::io::ErrorKind::InvalidInput`])
63//! - I/O issues while reading or writing files
64
65use std::fs::File;
66use std::io::{BufReader, BufWriter, Error, ErrorKind, Read, Result, Write};
67
68/// XOR's all bytes from the input path against the provided password then writes
69/// the result to the output path by proceeding in chunks of 8K.
70fn xor_process(input_path: &str, output_path: &str, password: &str) -> Result<()> {
71    // Opens input + output
72    let mut input_file = BufReader::new(File::open(input_path)?);
73    let mut output_file = BufWriter::new(File::create(output_path)?);
74
75    // Converts the password into bytes
76    let password_bytes = password.as_bytes();
77    if password_bytes.is_empty() {
78        return Err(Error::new(
79            ErrorKind::InvalidInput,
80            "The password cannot be empty",
81        ));
82    }
83
84    // Chunk buffer
85    let mut buffer = [0u8; 8192]; // 8K chunks
86    let mut offset = 0;
87
88    loop {
89        // Reads bytes up to 8K from the input file into the buffer, returns the number of bytes read
90        let bytes_read = input_file.read(&mut buffer)?;
91
92        // Stops the loop when no more bytes can be read from the file
93        if bytes_read == 0 {
94            break;
95        }
96
97        // XOR's each byte with the password
98        for i in 0..bytes_read {
99            buffer[i] ^= password_bytes[(offset + i) % password_bytes.len()];
100        }
101
102        // Writes chunk to the output file
103        output_file.write_all(&buffer[..bytes_read])?;
104        offset += bytes_read;
105    }
106
107    Ok(())
108}
109
110/// Encodes a file using XOR with the provided password and input path.
111/// Returns the output path of the encoded file.
112pub fn encode(input_path: &str, password: &str, output_path: Option<&str>) -> Result<String> {
113    let output = match output_path {
114        Some(path) => path.to_string(),
115        None => format!("{}.rxor", input_path),
116    };
117    xor_process(input_path, &output, password)?;
118    Ok(output)
119}
120
121/// Decodes a previously XOR-encoded file using the provided password.
122/// Returns the output path of the decoded file.
123pub fn decode(input_path: &str, password: &str, output_path: Option<&str>) -> Result<String> {
124    let output = match output_path {
125        Some(path) => path.to_string(),
126        None => {
127            if input_path.ends_with(".rxor") {
128                input_path.strip_suffix(".rxor").unwrap().to_string()
129            } else {
130                input_path.to_string()
131            }
132        }
133    };
134    xor_process(input_path, &output, password)?;
135    Ok(output)
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::fs::{read, remove_file, write};
142
143    #[test]
144    fn test_encode_decode() {
145        let input = "test.txt";
146        let password = "password123";
147        let original_content = b"Test ReXOR";
148
149        write(input, original_content).unwrap();
150
151        let encoded = encode(input, password, None).unwrap();
152        let decoded = decode(&encoded, password, None).unwrap();
153
154        let result = read(decoded).unwrap();
155        assert_eq!(result, original_content);
156
157        // Cleanup
158        remove_file(input).unwrap();
159        remove_file(encoded).unwrap();
160    }
161}