diff --git a/Cargo.toml b/Cargo.toml index 40d21d39..cdcc7fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "stegjs" readme = "README.md" repository = "https://github.com/andmev/stegjs" -version = "3.0.0" +version = "3.0.1" [lib] crate-type = ["cdylib"] @@ -24,7 +24,8 @@ napi = { version = "2.16.11", default-features = false, features = [ napi-derive = "2.16.12" openssl = { version = "0.10", features = ["vendored"] } regex = "1.10.6" -reqwest = { version = "0.12.7", features = ["json", "stream"] } +reqwest = { version = "0.12.8", features = ["json", "stream"] } +serde = { version = "1.0.210", features = ["derive"] } tokio = { version = "1.40.0", features = ["full"] } url = "2.5.2" diff --git a/README.md b/README.md index 5250093d..a6079ebd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![npm](https://img.shields.io/npm/v/stegjs.svg?maxAge=1)](https://www.npmjs.com/package/stegjs) [![npm](https://img.shields.io/npm/dt/stegjs.svg?maxAge=1)](https://www.npmjs.com/package/stegjs) ![Steg.js Test workflow](https://github.com/andmev/stegjs/actions/workflows/test.yml/badge.svg) [![npm](https://img.shields.io/npm/l/stegjs.svg?maxAge=1)](https://www.npmjs.com/package/stegjs) -> Command-line utility for steganography in PNG images. With this application you can send secret messages, passwords or other important information. +> A Node.js module and command-line utility for performing steganographic encoding in PNG images. This application enables the secure transmission of secret messages, passwords, or other critical information by embedding data within images. ## Installation @@ -13,8 +13,30 @@ To install globally you should enter in terminal window the following command: $ npm i -g stegjs ``` +To install locally you should enter in terminal window the following command: + +```sh +$ npm i stegjs +``` + ## Help +### Usage as Node.js module + +```js +const steg = require('stegjs') + +// Encode message +const response = steg.encode('img.png', 'my_secret_pass', '1x1', './secrets/go.png') +console.log(response) // -> { message: 'my_secret_pass', pattern: '1x1', output: './secrets/go.png' } + +// Decode message +const response = steg.decode('out.png') +console.log(response) // -> { message: 'my_secret_pass', pattern: '1x1' } +``` + +### Usage as CLI + ```sh $ stegjs --help diff --git a/__test__/decode.spec.ts b/__test__/decode.spec.ts index dbac50b0..d72f1340 100644 --- a/__test__/decode.spec.ts +++ b/__test__/decode.spec.ts @@ -4,8 +4,10 @@ import path from 'path' import { decode } from '../index.js' test('decode', async (t) => { - await decode(path.join(process.cwd(), '__test__', 'decode.png')) + const result = await decode(path.join(process.cwd(), '__test__', 'decode.png')) t.pass() + t.is(result.message, 'some-key') + t.is(result.pattern, '1x1') }) test('decode fails if image not exist', async (t) => { diff --git a/__test__/encode.spec.ts b/__test__/encode.spec.ts index b01d1672..df2f43ec 100644 --- a/__test__/encode.spec.ts +++ b/__test__/encode.spec.ts @@ -15,9 +15,12 @@ test.afterEach(() => { }) test('encode', async (t) => { - await encode(input, 'some-key', '1x1', output) + const result = await encode(input, 'some-key', '1x1', output) t.timeout(10000, 'make sure the test is not timeout') t.is(fs.existsSync(output), true) + t.is(result.pattern, '1x1') + t.is(result.message, 'some-key') + t.is(result.output, output) }) test('encode fails if image not exist', async (t) => { diff --git a/index.d.ts b/index.d.ts index e6e66fdf..7355cb97 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,7 +3,16 @@ /* auto-generated by NAPI-RS */ +export interface EncodeResult { + output: string + message: string + pattern: string +} +export interface DecodeResult { + message: string + pattern: string +} /** Exposes the `encode_rs` function to Node.js via N-API. */ -export declare function encode(image: string, message: string, step: string, output: string): Promise +export declare function encode(image: string, message: string, step: string, output: string): Promise /** Exposes the `decode_rs` function to Node.js via N-API. */ -export declare function decode(image: string): Promise +export declare function decode(image: string): Promise diff --git a/npm/android-arm-eabi/package.json b/npm/android-arm-eabi/package.json index fcda627a..a213f956 100644 --- a/npm/android-arm-eabi/package.json +++ b/npm/android-arm-eabi/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-android-arm-eabi", - "version": "3.0.0", + "version": "3.0.1", "os": [ "android" ], diff --git a/npm/android-arm64/package.json b/npm/android-arm64/package.json index 8bfbdf84..97146d35 100644 --- a/npm/android-arm64/package.json +++ b/npm/android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-android-arm64", - "version": "3.0.0", + "version": "3.0.1", "os": [ "android" ], diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json index 198d4bd4..18818131 100644 --- a/npm/darwin-arm64/package.json +++ b/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-darwin-arm64", - "version": "3.0.0", + "version": "3.0.1", "os": [ "darwin" ], diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json index d1862106..0f5ea80c 100644 --- a/npm/darwin-x64/package.json +++ b/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-darwin-x64", - "version": "3.0.0", + "version": "3.0.1", "os": [ "darwin" ], diff --git a/npm/freebsd-x64/package.json b/npm/freebsd-x64/package.json index c1b40c9b..2334a482 100644 --- a/npm/freebsd-x64/package.json +++ b/npm/freebsd-x64/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-freebsd-x64", - "version": "3.0.0", + "version": "3.0.1", "os": [ "freebsd" ], diff --git a/npm/linux-arm-gnueabihf/package.json b/npm/linux-arm-gnueabihf/package.json index 07789084..830d9900 100644 --- a/npm/linux-arm-gnueabihf/package.json +++ b/npm/linux-arm-gnueabihf/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-linux-arm-gnueabihf", - "version": "3.0.0", + "version": "3.0.1", "os": [ "linux" ], diff --git a/npm/linux-arm64-gnu/package.json b/npm/linux-arm64-gnu/package.json index f68e033d..da00c60d 100644 --- a/npm/linux-arm64-gnu/package.json +++ b/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-linux-arm64-gnu", - "version": "3.0.0", + "version": "3.0.1", "os": [ "linux" ], diff --git a/npm/linux-arm64-musl/package.json b/npm/linux-arm64-musl/package.json index 54b73999..0a5328f9 100644 --- a/npm/linux-arm64-musl/package.json +++ b/npm/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-linux-arm64-musl", - "version": "3.0.0", + "version": "3.0.1", "os": [ "linux" ], diff --git a/npm/linux-x64-gnu/package.json b/npm/linux-x64-gnu/package.json index 55565451..755714a3 100644 --- a/npm/linux-x64-gnu/package.json +++ b/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-linux-x64-gnu", - "version": "3.0.0", + "version": "3.0.1", "os": [ "linux" ], diff --git a/npm/linux-x64-musl/package.json b/npm/linux-x64-musl/package.json index 49b045d9..ebea86ea 100644 --- a/npm/linux-x64-musl/package.json +++ b/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-linux-x64-musl", - "version": "3.0.0", + "version": "3.0.1", "os": [ "linux" ], diff --git a/npm/win32-arm64-msvc/package.json b/npm/win32-arm64-msvc/package.json index e9580e88..12dbf279 100644 --- a/npm/win32-arm64-msvc/package.json +++ b/npm/win32-arm64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-win32-arm64-msvc", - "version": "3.0.0", + "version": "3.0.1", "os": [ "win32" ], diff --git a/npm/win32-ia32-msvc/package.json b/npm/win32-ia32-msvc/package.json index 70f31689..e8037111 100644 --- a/npm/win32-ia32-msvc/package.json +++ b/npm/win32-ia32-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-win32-ia32-msvc", - "version": "3.0.0", + "version": "3.0.1", "os": [ "win32" ], diff --git a/npm/win32-x64-msvc/package.json b/npm/win32-x64-msvc/package.json index 785827b1..44b2339a 100644 --- a/npm/win32-x64-msvc/package.json +++ b/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@andmev/stegjs-win32-x64-msvc", - "version": "3.0.0", + "version": "3.0.1", "os": [ "win32" ], diff --git a/package.json b/package.json index 23eb6f76..f183b128 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stegjs", - "version": "3.0.0", + "version": "3.0.1", "description": "Encrypt message to PNG image.", "main": "index.js", "types": "index.d.ts", diff --git a/src/decode.rs b/src/decode.rs index b41d5f6c..8de412d8 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -3,6 +3,7 @@ use std::error::Error; use crate::checker::{is_png, is_uri}; use crate::converters::{bits_to_string, meta_to_obj}; use crate::get_file::{by_path, by_uri}; +use crate::DecodeResult; // Add this import /// Decodes a hidden message from a PNG image using steganography. /// @@ -12,7 +13,7 @@ use crate::get_file::{by_path, by_uri}; /// # Errors /// Returns an error if the input image is not a valid PNG, if decoding fails, /// or if there are issues reading the files. -pub async fn decode_rs(img: &str) -> Result<(), Box> { +pub async fn decode_rs(img: &str) -> Result> { // Check that the input file is in PNG format. if !is_png(img) { return Err("Only *.png images are supported.".into()); @@ -26,9 +27,9 @@ pub async fn decode_rs(img: &str) -> Result<(), Box> { }; // Proceed to decode the message from the image. - decode_image(&file_path)?; + let (message, pattern) = decode_image(&file_path)?; - Ok(()) + Ok(DecodeResult { message, pattern }) } /// Helper function to decode the message from the image. @@ -38,14 +39,14 @@ pub async fn decode_rs(img: &str) -> Result<(), Box> { /// /// # Errors /// Returns an error if decoding fails or if the message cannot be reconstructed. -fn decode_image(file_path: &str) -> Result<(), Box> { +fn decode_image(file_path: &str) -> Result<(String, String), Box> { // Load the image from the specified path. let img = image::open(file_path)?.to_rgba8(); let (width, height) = img.dimensions(); // Define the number of bits to read for meta-information. - // Assuming meta string is 8 characters: "10|10" - let meta_str_length = 8; + // Assuming meta string is 8 characters: "10|XX" + let meta_str_length = 8; // Adjusted to match the meta format in encode.rs let meta_bits_len = meta_str_length * 8; // Extract meta bits @@ -117,20 +118,23 @@ fn decode_image(file_path: &str) -> Result<(), Box> { // Convert message bits to string let message = bits_to_string(&message_bits)?; - // Output the decoded message + // Construct the pattern string + let pattern = format!("{}x{}", width_step_num, height_step_num); + + // Output the decoded message and pattern println!( - "{} was decoded!\nmessage: {}\npattern: {}x{}", - file_path, message, width_step_num, height_step_num + "{} was decoded!\nmessage: {}\npattern: {}", + file_path, message, pattern ); - Ok(()) + Ok((message, pattern)) } #[cfg(test)] mod tests { use super::*; use crate::encode_rs; - use image::{GenericImageView, Rgba, RgbaImage}; + use image::{Rgba, RgbaImage}; use tempfile::NamedTempFile; #[tokio::test] @@ -147,7 +151,7 @@ mod tests { let test_message = "Test message"; // Encode the message - encode_rs( + let encode_result = encode_rs( temp_in.path().with_extension("png").to_str().unwrap(), test_message, "1x1", @@ -156,19 +160,23 @@ mod tests { .await .expect("Failed to encode message"); - // Print the contents of the encoded image - let encoded_img = image::open(temp_out.path().with_extension("png")).unwrap(); - println!("Encoded image dimensions: {:?}", encoded_img.dimensions()); + // Verify the encode result + assert_eq!( + encode_result.output, + temp_out.path().with_extension("png").to_str().unwrap() + ); + assert_eq!(encode_result.message, test_message); + assert_eq!(encode_result.pattern, "1x1"); // Decode the message - let result = decode_rs(temp_out.path().with_extension("png").to_str().unwrap()).await; + let decode_result = decode_rs(temp_out.path().with_extension("png").to_str().unwrap()) + .await + .expect("Failed to decode message"); - match result { - Ok(_) => println!("Decoding successful"), - Err(e) => { - println!("Decoding failed: {:?}", e); - panic!("Decoding failed: {:?}", e); - } - } + // Verify the decode result + assert_eq!(decode_result.message, test_message); + assert_eq!(decode_result.pattern, "1x1"); + + println!("Decoding test successful."); } } diff --git a/src/encode.rs b/src/encode.rs index 0d614e44..6b1ce3d8 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -1,8 +1,8 @@ -use std::error::Error; - use crate::checker::{is_png, is_right_step, is_uri}; use crate::converters::string_to_bits; use crate::get_file::{by_path, by_uri}; +use crate::EncodeResult; +use std::error::Error; // Add this import /// Encodes a message into a PNG image using steganography. /// @@ -15,7 +15,12 @@ use crate::get_file::{by_path, by_uri}; /// # Errors /// Returns an error if the input image is not a valid PNG, if the message does not fit, /// or if there are issues reading or writing the files. -pub async fn encode_rs(img: &str, msg: &str, step: &str, out: &str) -> Result<(), Box> { +pub async fn encode_rs( + img: &str, + msg: &str, + step: &str, + out: &str, +) -> Result> { // Check that the input file is in PNG format. if !is_png(img) { return Err("Only *.png images are supported.".into()); @@ -31,7 +36,11 @@ pub async fn encode_rs(img: &str, msg: &str, step: &str, out: &str) -> Result<() // Proceed to encode the message into the image. encode_image(&file_path, msg, step, out)?; - Ok(()) + Ok(EncodeResult { + output: out.to_string(), + message: msg.to_string(), + pattern: step.to_string(), + }) } /// Helper function to encode the message into the image. @@ -149,7 +158,7 @@ mod tests { let test_message = "Test message"; // Encode the message - encode_rs( + let result = encode_rs( temp_in.path().with_extension("png").to_str().unwrap(), test_message, "1x1", @@ -158,8 +167,16 @@ mod tests { .await .expect("Failed to encode message"); + // Verify the result + assert_eq!( + result.output, + temp_out.path().with_extension("png").to_str().unwrap() + ); + assert_eq!(result.message, test_message); + assert_eq!(result.pattern, "1x1"); + // Load the encoded image - let encoded_img = image::open(temp_out.path().with_extension("png")).unwrap(); + let encoded_img = image::open(&result.output).unwrap(); // Verify that the image was saved correctly assert_eq!(encoded_img.dimensions(), (100, 100)); diff --git a/src/lib.rs b/src/lib.rs index c488f525..c3939831 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,14 +11,33 @@ pub use decode::*; pub use encode::*; pub use get_file::*; -#[macro_use] -extern crate napi_derive; +use napi::{Error as NapiError, Error}; +use napi_derive::napi; +use serde::Serialize; -use napi::{Error as NapiError, Result}; +#[napi(object)] +#[derive(Serialize)] +pub struct EncodeResult { + pub output: String, + pub message: String, + pub pattern: String, +} + +#[napi(object)] +#[derive(Serialize)] +pub struct DecodeResult { + pub message: String, + pub pattern: String, +} /// Exposes the `encode_rs` function to Node.js via N-API. #[napi] -pub async fn encode(image: String, message: String, step: String, output: String) -> Result<()> { +pub async fn encode( + image: String, + message: String, + step: String, + output: String, +) -> std::result::Result { encode_rs(&image, &message, &step, &output) .await .map_err(|e| NapiError::from_reason(e.to_string())) @@ -26,7 +45,7 @@ pub async fn encode(image: String, message: String, step: String, output: String /// Exposes the `decode_rs` function to Node.js via N-API. #[napi] -pub async fn decode(image: String) -> Result<()> { +pub async fn decode(image: String) -> std::result::Result { decode_rs(&image) .await .map_err(|e| NapiError::from_reason(e.to_string()))