Implement CLI encode/decode with reference photo support

- Add required -r/--reference option to encode command
- Add required -r/--reference option to decode command
- Replace stub implementations with actual library calls
- CLI now properly encodes and decodes messages/files
- Fix smoke test form field names and add proper redirect handling

The CLI encode/decode were stubs that didn't actually work.
Now they properly use the stegasoo library functions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-04 20:25:59 -05:00
parent c965a5f8da
commit 710b3a6a98
2 changed files with 414 additions and 269 deletions

View File

@@ -20,23 +20,23 @@ NC='\033[0m'
PI_IP="${1:-192.168.0.4}" PI_IP="${1:-192.168.0.4}"
HTTPS=false HTTPS=false
if [[ "$2" == "--https" ]] || [[ "$1" == "--https" ]]; then if [[ "$2" == "--https" ]] || [[ "$1" == "--https" ]]; then
HTTPS=true HTTPS=true
if [[ "$1" == "--https" ]]; then if [[ "$1" == "--https" ]]; then
PI_IP="192.168.0.4" PI_IP="192.168.0.4"
fi fi
fi fi
if [ "$HTTPS" = true ]; then if [ "$HTTPS" = true ]; then
BASE_URL="https://$PI_IP:5000" BASE_URL="https://$PI_IP:5000"
CURL_OPTS="-k" # Allow self-signed certs CURL_OPTS="-k" # Allow self-signed certs
else else
BASE_URL="http://$PI_IP:5000" BASE_URL="http://$PI_IP:5000"
CURL_OPTS="" CURL_OPTS=""
fi fi
# Test credentials # Test credentials
ADMIN_USER="smokeadmin" ADMIN_USER="admin"
ADMIN_PASS="SmokeAdmin123!" ADMIN_PASS="stegasoo"
REGULAR_USER="smokeuser" REGULAR_USER="smokeuser"
REGULAR_PASS="SmokeUser123!" REGULAR_PASS="SmokeUser123!"
@@ -51,24 +51,24 @@ ENCODED_IMAGE_USER=$(mktemp --suffix=.png)
QR_IMAGE=$(mktemp --suffix=.png) QR_IMAGE=$(mktemp --suffix=.png)
cleanup() { cleanup() {
rm -f "$COOKIE_JAR" "$COOKIE_JAR_USER" "$TEST_IMAGE" "$ENCODED_IMAGE" "$ENCODED_IMAGE_USER" "$QR_IMAGE" "$RESPONSE" rm -f "$COOKIE_JAR" "$COOKIE_JAR_USER" "$TEST_IMAGE" "$ENCODED_IMAGE" "$ENCODED_IMAGE_USER" "$QR_IMAGE" "$RESPONSE"
} }
trap cleanup EXIT trap cleanup EXIT
# Create a simple test image (red square) # Create a simple test image (red square)
create_test_image() { create_test_image() {
if command -v convert &>/dev/null; then if command -v convert &>/dev/null; then
convert -size 100x100 xc:red "$TEST_IMAGE" convert -size 100x100 xc:red "$TEST_IMAGE"
elif command -v python3 &>/dev/null; then elif command -v python3 &>/dev/null; then
python3 -c " python3 -c "
from PIL import Image from PIL import Image
img = Image.new('RGB', (100, 100), color='red') img = Image.new('RGB', (100, 100), color='red')
img.save('$TEST_IMAGE') img.save('$TEST_IMAGE')
" "
else else
echo -e "${YELLOW}Warning: No image tool available, skipping encode/decode tests${NC}" echo -e "${YELLOW}Warning: No image tool available, skipping encode/decode tests${NC}"
return 1 return 1
fi fi
} }
# Results tracking # Results tracking
@@ -76,17 +76,17 @@ TESTS_PASSED=0
TESTS_FAILED=0 TESTS_FAILED=0
pass() { pass() {
echo -e " ${GREEN}[PASS]${NC} $1" echo -e " ${GREEN}[PASS]${NC} $1"
TESTS_PASSED=$((TESTS_PASSED + 1)) TESTS_PASSED=$((TESTS_PASSED + 1))
} }
fail() { fail() {
echo -e " ${RED}[FAIL]${NC} $1" echo -e " ${RED}[FAIL]${NC} $1"
TESTS_FAILED=$((TESTS_FAILED + 1)) TESTS_FAILED=$((TESTS_FAILED + 1))
} }
skip() { skip() {
echo -e " ${YELLOW}[SKIP]${NC} $1" echo -e " ${YELLOW}[SKIP]${NC} $1"
} }
# ============================================================================= # =============================================================================
@@ -108,30 +108,30 @@ echo ""
echo -e "${BOLD}[1/9] Web UI Accessibility${NC}" echo -e "${BOLD}[1/9] Web UI Accessibility${NC}"
if curl $CURL_OPTS -s -o /dev/null -w "%{http_code}" "$BASE_URL" | grep -q "200\|302"; then if curl $CURL_OPTS -s -o /dev/null -w "%{http_code}" "$BASE_URL" | grep -q "200\|302"; then
pass "Web UI is reachable" pass "Web UI is reachable"
else else
fail "Web UI not reachable at $BASE_URL" fail "Web UI not reachable at $BASE_URL"
echo -e "${RED}Cannot continue without web access. Is the Pi running?${NC}" echo -e "${RED}Cannot continue without web access. Is the Pi running?${NC}"
exit 1 exit 1
fi fi
# Check if redirected to setup (first run) or login # Check if redirected to setup (first run) or login
REDIRECT=$(curl $CURL_OPTS -s -o /dev/null -w "%{redirect_url}" "$BASE_URL") REDIRECT=$(curl $CURL_OPTS -s -o /dev/null -w "%{redirect_url}" "$BASE_URL")
if echo "$REDIRECT" | grep -q "setup"; then if echo "$REDIRECT" | grep -q "setup"; then
pass "Redirected to setup (fresh install)" pass "Redirected to setup (fresh install)"
NEEDS_SETUP=true NEEDS_SETUP=true
elif echo "$REDIRECT" | grep -q "login"; then elif echo "$REDIRECT" | grep -q "login"; then
pass "Redirected to login (already configured)" pass "Redirected to login (already configured)"
NEEDS_SETUP=false NEEDS_SETUP=false
else else
# Check page content # Check page content
if curl $CURL_OPTS -s "$BASE_URL" | grep -q "setup\|Setup\|Create.*Admin"; then if curl $CURL_OPTS -s "$BASE_URL" | grep -q "setup\|Setup\|Create.*Admin"; then
pass "Setup page detected" pass "Setup page detected"
NEEDS_SETUP=true NEEDS_SETUP=true
else else
pass "Login page detected" pass "Login page detected"
NEEDS_SETUP=false NEEDS_SETUP=false
fi fi
fi fi
# ============================================================================= # =============================================================================
@@ -142,35 +142,35 @@ echo ""
echo -e "${BOLD}[2/9] Admin Setup${NC}" echo -e "${BOLD}[2/9] Admin Setup${NC}"
if [ "$NEEDS_SETUP" = true ]; then if [ "$NEEDS_SETUP" = true ]; then
# Get CSRF token from setup page # Get CSRF token from setup page
SETUP_PAGE=$(curl $CURL_OPTS -s -c "$COOKIE_JAR" "$BASE_URL/setup") SETUP_PAGE=$(curl $CURL_OPTS -s -c "$COOKIE_JAR" "$BASE_URL/setup")
CSRF_TOKEN=$(echo "$SETUP_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") CSRF_TOKEN=$(echo "$SETUP_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
if [ -z "$CSRF_TOKEN" ]; then if [ -z "$CSRF_TOKEN" ]; then
# Try alternate pattern # Try alternate pattern
CSRF_TOKEN=$(echo "$SETUP_PAGE" | grep -oP 'csrf_token.*?value="\K[^"]+' || echo "") CSRF_TOKEN=$(echo "$SETUP_PAGE" | grep -oP 'csrf_token.*?value="\K[^"]+' || echo "")
fi fi
# Create admin user # Create admin user
HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \ HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \
-b "$COOKIE_JAR" -c "$COOKIE_JAR" \ -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
-X POST "$BASE_URL/setup" \ -X POST "$BASE_URL/setup" \
-d "username=$ADMIN_USER" \ -d "username=$ADMIN_USER" \
-d "password=$ADMIN_PASS" \ -d "password=$ADMIN_PASS" \
-d "password_confirm=$ADMIN_PASS" \ -d "password_confirm=$ADMIN_PASS" \
-d "csrf_token=$CSRF_TOKEN") -d "csrf_token=$CSRF_TOKEN")
if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then
if curl $CURL_OPTS -s "$BASE_URL" | grep -q "login\|Login"; then if curl $CURL_OPTS -s "$BASE_URL" | grep -q "login\|Login"; then
pass "Admin user created successfully" pass "Admin user created successfully"
else
pass "Setup completed (assuming success)"
fi
else else
fail "Failed to create admin user (HTTP $HTTP_CODE)" pass "Setup completed (assuming success)"
fi fi
else
fail "Failed to create admin user (HTTP $HTTP_CODE)"
fi
else else
skip "Setup already complete" skip "Setup already complete"
fi fi
# ============================================================================= # =============================================================================
@@ -186,20 +186,20 @@ CSRF_TOKEN=$(echo "$LOGIN_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+
# Try login as admin # Try login as admin
HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \ HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \
-b "$COOKIE_JAR" -c "$COOKIE_JAR" \ -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
-X POST "$BASE_URL/login" \ -X POST "$BASE_URL/login" \
-d "username=$ADMIN_USER" \ -d "username=$ADMIN_USER" \
-d "password=$ADMIN_PASS" \ -d "password=$ADMIN_PASS" \
-d "csrf_token=$CSRF_TOKEN" \ -d "csrf_token=$CSRF_TOKEN" \
-L) -L)
# Check if we're logged in by accessing a protected page # Check if we're logged in by accessing a protected page
if curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/" | grep -qi "encode\|decode\|logout"; then if curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/" | grep -qi "encode\|decode\|logout"; then
pass "Admin login successful" pass "Admin login successful"
ADMIN_LOGGED_IN=true ADMIN_LOGGED_IN=true
else else
fail "Admin login failed" fail "Admin login failed"
ADMIN_LOGGED_IN=false ADMIN_LOGGED_IN=false
fi fi
# ============================================================================= # =============================================================================
@@ -210,55 +210,84 @@ echo ""
echo -e "${BOLD}[4/9] Admin Encode/Decode${NC}" echo -e "${BOLD}[4/9] Admin Encode/Decode${NC}"
if [ "$ADMIN_LOGGED_IN" = true ]; then if [ "$ADMIN_LOGGED_IN" = true ]; then
ENCODE_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/encode") ENCODE_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/encode")
if echo "$ENCODE_PAGE" | grep -qi "encode\|message\|image\|upload"; then if echo "$ENCODE_PAGE" | grep -qi "encode\|message\|image\|upload"; then
pass "Encode page loads" pass "Encode page loads"
else else
fail "Encode page not accessible" fail "Encode page not accessible"
fi
# Try actual encoding if we have image tools
if create_test_image 2>/dev/null; then
CSRF_TOKEN=$(echo "$ENCODE_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
# For encode: use same image as reference_photo and carrier (for simplicity)
# First POST (no redirect follow), get Location header, then GET result page
ENCODE_RESULT=$(curl $CURL_OPTS -s -D - -o /dev/null \
-b "$COOKIE_JAR" -c "$COOKIE_JAR" \
-X POST "$BASE_URL/encode" \
-F "reference_photo=@$TEST_IMAGE" \
-F "carrier=@$TEST_IMAGE" \
-F "message=Admin smoke test" \
-F "passphrase=smoke test phrase" \
-F "pin=123456" \
-F "csrf_token=$CSRF_TOKEN")
# Extract redirect location
RESULT_LOCATION=$(echo "$ENCODE_RESULT" | grep -i "^location:" | tr -d '\r' | awk '{print $2}')
if [ -n "$RESULT_LOCATION" ]; then
# GET the result page
RESULT_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL$RESULT_LOCATION")
# Look for download link in result page
DOWNLOAD_URL=$(echo "$RESULT_PAGE" | grep -oP 'href="(/encode/download/[^"]+)"' | head -1 | grep -oP '/encode/download/[^"]+')
fi fi
# Try actual encoding if we have image tools if [ -n "$DOWNLOAD_URL" ]; then
if create_test_image 2>/dev/null; then # Download the encoded image
CSRF_TOKEN=$(echo "$ENCODE_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") HTTP_CODE=$(curl $CURL_OPTS -s -o "$ENCODED_IMAGE" -w "%{http_code}" \
-b "$COOKIE_JAR" "$BASE_URL$DOWNLOAD_URL")
HTTP_CODE=$(curl $CURL_OPTS -s -o "$ENCODED_IMAGE" -w "%{http_code}" \ if [ "$HTTP_CODE" = "200" ] && file "$ENCODED_IMAGE" | grep -qi "image\|PNG\|JPEG"; then
-b "$COOKIE_JAR" \ pass "Admin encoding works"
-X POST "$BASE_URL/encode" \
-F "image=@$TEST_IMAGE" \
-F "message=Admin smoke test" \
-F "csrf_token=$CSRF_TOKEN")
if [ "$HTTP_CODE" = "200" ] && [ -s "$ENCODED_IMAGE" ]; then # Now decode it
if file "$ENCODED_IMAGE" | grep -qi "image\|PNG\|JPEG"; then DECODE_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/decode")
pass "Admin encoding works" CSRF_TOKEN=$(echo "$DECODE_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
# Now decode it DECODED=$(curl $CURL_OPTS -s \
DECODE_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/decode") -b "$COOKIE_JAR" \
CSRF_TOKEN=$(echo "$DECODE_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") -X POST "$BASE_URL/decode" \
-F "reference_photo=@$TEST_IMAGE" \
-F "stego_image=@$ENCODED_IMAGE" \
-F "passphrase=smoke test phrase" \
-F "pin=123456" \
-F "csrf_token=$CSRF_TOKEN")
DECODED=$(curl $CURL_OPTS -s \ if echo "$DECODED" | grep -q "Admin smoke test"; then
-b "$COOKIE_JAR" \ pass "Admin decoding works"
-X POST "$BASE_URL/decode" \
-F "image=@$ENCODED_IMAGE" \
-F "csrf_token=$CSRF_TOKEN")
if echo "$DECODED" | grep -q "Admin smoke test"; then
pass "Admin decoding works"
else
fail "Admin decode failed"
fi
else
fail "Encoding returned non-image response"
fi
else else
fail "Encoding request failed (HTTP $HTTP_CODE)" fail "Admin decode failed"
fi fi
else
fail "Failed to download encoded image (HTTP $HTTP_CODE)"
fi
else else
skip "Encode/Decode (no image tools)" # Check for error messages in result page
ERROR_MSG=$(echo "$RESULT_PAGE" | grep -oP 'toast-body">[^<]*<[^>]*>[^<]*' | head -1)
if [ -n "$ERROR_MSG" ]; then
fail "Encoding failed: $ERROR_MSG"
else
fail "No download link found in encode result"
fi
fi fi
else
skip "Encode/Decode (no image tools)"
fi
else else
skip "Admin encode/decode (not logged in)" skip "Admin encode/decode (not logged in)"
fi fi
# ============================================================================= # =============================================================================
@@ -269,48 +298,48 @@ echo ""
echo -e "${BOLD}[5/9] Create Regular User${NC}" echo -e "${BOLD}[5/9] Create Regular User${NC}"
if [ "$ADMIN_LOGGED_IN" = true ]; then if [ "$ADMIN_LOGGED_IN" = true ]; then
# Check if there's a user management page # Check if there's a user management page
USERS_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/users" 2>/dev/null || echo "") USERS_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/users" 2>/dev/null || echo "")
if echo "$USERS_PAGE" | grep -qi "user\|create\|add"; then if echo "$USERS_PAGE" | grep -qi "user\|create\|add"; then
CSRF_TOKEN=$(echo "$USERS_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") CSRF_TOKEN=$(echo "$USERS_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \ HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \
-b "$COOKIE_JAR" \ -b "$COOKIE_JAR" \
-X POST "$BASE_URL/users/create" \ -X POST "$BASE_URL/users/create" \
-d "username=$REGULAR_USER" \ -d "username=$REGULAR_USER" \
-d "password=$REGULAR_PASS" \ -d "password=$REGULAR_PASS" \
-d "password_confirm=$REGULAR_PASS" \ -d "password_confirm=$REGULAR_PASS" \
-d "csrf_token=$CSRF_TOKEN") -d "csrf_token=$CSRF_TOKEN")
if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then
pass "Regular user created" pass "Regular user created"
USER_CREATED=true USER_CREATED=true
else
# Try alternate endpoint
HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \
-b "$COOKIE_JAR" \
-X POST "$BASE_URL/register" \
-d "username=$REGULAR_USER" \
-d "password=$REGULAR_PASS" \
-d "password_confirm=$REGULAR_PASS" \
-d "csrf_token=$CSRF_TOKEN")
if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then
pass "Regular user created (via register)"
USER_CREATED=true
else
fail "Failed to create regular user"
USER_CREATED=false
fi
fi
else else
skip "User creation (no user management page)" # Try alternate endpoint
HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \
-b "$COOKIE_JAR" \
-X POST "$BASE_URL/register" \
-d "username=$REGULAR_USER" \
-d "password=$REGULAR_PASS" \
-d "password_confirm=$REGULAR_PASS" \
-d "csrf_token=$CSRF_TOKEN")
if [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "200" ]; then
pass "Regular user created (via register)"
USER_CREATED=true
else
fail "Failed to create regular user"
USER_CREATED=false USER_CREATED=false
fi
fi fi
else else
skip "User creation (admin not logged in)" skip "User creation (no user management page)"
USER_CREATED=false USER_CREATED=false
fi
else
skip "User creation (admin not logged in)"
USER_CREATED=false
fi fi
# ============================================================================= # =============================================================================
@@ -321,47 +350,50 @@ echo ""
echo -e "${BOLD}[6/9] Regular User Workflow${NC}" echo -e "${BOLD}[6/9] Regular User Workflow${NC}"
if [ "$USER_CREATED" = true ]; then if [ "$USER_CREATED" = true ]; then
# Logout admin first (get fresh session) # Logout admin first (get fresh session)
curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/logout" > /dev/null curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/logout" >/dev/null
# Login as regular user # Login as regular user
LOGIN_PAGE=$(curl $CURL_OPTS -s -c "$COOKIE_JAR_USER" "$BASE_URL/login") LOGIN_PAGE=$(curl $CURL_OPTS -s -c "$COOKIE_JAR_USER" "$BASE_URL/login")
CSRF_TOKEN=$(echo "$LOGIN_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") CSRF_TOKEN=$(echo "$LOGIN_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \ HTTP_CODE=$(curl $CURL_OPTS -s -o "$RESPONSE" -w "%{http_code}" \
-b "$COOKIE_JAR_USER" -c "$COOKIE_JAR_USER" \ -b "$COOKIE_JAR_USER" -c "$COOKIE_JAR_USER" \
-X POST "$BASE_URL/login" \ -X POST "$BASE_URL/login" \
-d "username=$REGULAR_USER" \ -d "username=$REGULAR_USER" \
-d "password=$REGULAR_PASS" \ -d "password=$REGULAR_PASS" \
-d "csrf_token=$CSRF_TOKEN" \ -d "csrf_token=$CSRF_TOKEN" \
-L) -L)
if curl $CURL_OPTS -s -b "$COOKIE_JAR_USER" "$BASE_URL/" | grep -qi "encode\|decode\|logout"; then if curl $CURL_OPTS -s -b "$COOKIE_JAR_USER" "$BASE_URL/" | grep -qi "encode\|decode\|logout"; then
pass "Regular user login successful" pass "Regular user login successful"
# Try encode/decode as regular user # Try encode/decode as regular user
if [ -f "$TEST_IMAGE" ]; then if [ -f "$TEST_IMAGE" ]; then
ENCODE_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR_USER" "$BASE_URL/encode") ENCODE_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR_USER" "$BASE_URL/encode")
CSRF_TOKEN=$(echo "$ENCODE_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") CSRF_TOKEN=$(echo "$ENCODE_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
HTTP_CODE=$(curl $CURL_OPTS -s -o "$ENCODED_IMAGE_USER" -w "%{http_code}" \ HTTP_CODE=$(curl $CURL_OPTS -s -o "$ENCODED_IMAGE_USER" -w "%{http_code}" \
-b "$COOKIE_JAR_USER" \ -b "$COOKIE_JAR_USER" \
-X POST "$BASE_URL/encode" \ -X POST "$BASE_URL/encode" \
-F "image=@$TEST_IMAGE" \ -F "reference_photo=@$TEST_IMAGE" \
-F "message=User smoke test" \ -F "carrier=@$TEST_IMAGE" \
-F "csrf_token=$CSRF_TOKEN") -F "message=User smoke test" \
-F "passphrase=user test phrase" \
-F "pin=567890" \
-F "csrf_token=$CSRF_TOKEN")
if [ "$HTTP_CODE" = "200" ] && [ -s "$ENCODED_IMAGE_USER" ]; then if [ "$HTTP_CODE" = "200" ] && [ -s "$ENCODED_IMAGE_USER" ] && file "$ENCODED_IMAGE_USER" | grep -qi "image\|PNG"; then
pass "Regular user encoding works" pass "Regular user encoding works"
else else
fail "Regular user encoding failed" fail "Regular user encoding failed"
fi fi
fi
else
fail "Regular user login failed"
fi fi
else
fail "Regular user login failed"
fi
else else
skip "Regular user workflow (user not created)" skip "Regular user workflow (user not created)"
fi fi
# ============================================================================= # =============================================================================
@@ -376,45 +408,45 @@ LOGIN_PAGE=$(curl $CURL_OPTS -s -c "$COOKIE_JAR" "$BASE_URL/login")
CSRF_TOKEN=$(echo "$LOGIN_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "") CSRF_TOKEN=$(echo "$LOGIN_PAGE" | grep -oP 'name="csrf_token"[^>]*value="\K[^"]+' || echo "")
curl $CURL_OPTS -s -o /dev/null \ curl $CURL_OPTS -s -o /dev/null \
-b "$COOKIE_JAR" -c "$COOKIE_JAR" \ -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
-X POST "$BASE_URL/login" \ -X POST "$BASE_URL/login" \
-d "username=$ADMIN_USER" \ -d "username=$ADMIN_USER" \
-d "password=$ADMIN_PASS" \ -d "password=$ADMIN_PASS" \
-d "csrf_token=$CSRF_TOKEN" \ -d "csrf_token=$CSRF_TOKEN" \
-L -L
# Check for recovery QR endpoint # Check for recovery QR endpoint
RECOVERY_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/recovery" 2>/dev/null || \ RECOVERY_PAGE=$(curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/recovery" 2>/dev/null ||
curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/settings" 2>/dev/null || \ curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/settings" 2>/dev/null ||
curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/account" 2>/dev/null || echo "") curl $CURL_OPTS -s -b "$COOKIE_JAR" "$BASE_URL/account" 2>/dev/null || echo "")
if echo "$RECOVERY_PAGE" | grep -qi "recovery\|qr\|backup"; then if echo "$RECOVERY_PAGE" | grep -qi "recovery\|qr\|backup"; then
pass "Recovery page accessible" pass "Recovery page accessible"
# Try to get QR image # Try to get QR image
QR_URL=$(echo "$RECOVERY_PAGE" | grep -oP 'src="[^"]*qr[^"]*"' | head -1 | sed 's/src="//;s/"$//' || echo "") QR_URL=$(echo "$RECOVERY_PAGE" | grep -oP 'src="[^"]*qr[^"]*"' | head -1 | sed 's/src="//;s/"$//' || echo "")
if [ -n "$QR_URL" ]; then if [ -n "$QR_URL" ]; then
if [[ "$QR_URL" != http* ]]; then if [[ "$QR_URL" != http* ]]; then
QR_URL="$BASE_URL$QR_URL" QR_URL="$BASE_URL$QR_URL"
fi
HTTP_CODE=$(curl $CURL_OPTS -s -o "$QR_IMAGE" -w "%{http_code}" -b "$COOKIE_JAR" "$QR_URL")
if [ "$HTTP_CODE" = "200" ] && [ -s "$QR_IMAGE" ]; then
if file "$QR_IMAGE" | grep -qi "image\|PNG"; then
pass "Recovery QR code generated"
else
fail "QR endpoint returned non-image"
fi
else
fail "Failed to fetch QR code"
fi
else
skip "QR code URL not found in page"
fi fi
HTTP_CODE=$(curl $CURL_OPTS -s -o "$QR_IMAGE" -w "%{http_code}" -b "$COOKIE_JAR" "$QR_URL")
if [ "$HTTP_CODE" = "200" ] && [ -s "$QR_IMAGE" ]; then
if file "$QR_IMAGE" | grep -qi "image\|PNG"; then
pass "Recovery QR code generated"
else
fail "QR endpoint returned non-image"
fi
else
fail "Failed to fetch QR code"
fi
else
skip "QR code URL not found in page"
fi
else else
skip "Password recovery (no recovery page found)" skip "Password recovery (no recovery page found)"
fi fi
# ============================================================================= # =============================================================================
@@ -426,30 +458,30 @@ echo -e "${BOLD}[8/9] System Health${NC}"
# Check if stegasoo CLI works via SSH (optional) # Check if stegasoo CLI works via SSH (optional)
if command -v sshpass &>/dev/null; then if command -v sshpass &>/dev/null; then
CLI_VERSION=$(sshpass -p 'stegasoo' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ CLI_VERSION=$(sshpass -p 'stegasoo' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
admin@$PI_IP "stegasoo --version" 2>/dev/null || echo "") admin@$PI_IP "stegasoo --version" 2>/dev/null || echo "")
if [ -n "$CLI_VERSION" ]; then if [ -n "$CLI_VERSION" ]; then
pass "CLI accessible: $CLI_VERSION" pass "CLI accessible: $CLI_VERSION"
else else
skip "CLI check (SSH failed or CLI not in PATH)" skip "CLI check (SSH failed or CLI not in PATH)"
fi fi
else else
skip "CLI check (sshpass not installed)" skip "CLI check (sshpass not installed)"
fi fi
# Check service status via SSH # Check service status via SSH
if command -v sshpass &>/dev/null; then if command -v sshpass &>/dev/null; then
SERVICE_STATUS=$(sshpass -p 'stegasoo' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ SERVICE_STATUS=$(sshpass -p 'stegasoo' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
admin@$PI_IP "systemctl is-active stegasoo" 2>/dev/null || echo "unknown") admin@$PI_IP "systemctl is-active stegasoo" 2>/dev/null || echo "unknown")
if [ "$SERVICE_STATUS" = "active" ]; then if [ "$SERVICE_STATUS" = "active" ]; then
pass "Stegasoo service is active" pass "Stegasoo service is active"
else else
fail "Stegasoo service status: $SERVICE_STATUS" fail "Stegasoo service status: $SERVICE_STATUS"
fi fi
else else
skip "Service check (sshpass not installed)" skip "Service check (sshpass not installed)"
fi fi
# ============================================================================= # =============================================================================
@@ -461,9 +493,9 @@ echo -e "${BOLD}[9/9] Cleanup${NC}"
# Just verify we can still access the site # Just verify we can still access the site
if curl $CURL_OPTS -s -o /dev/null -w "%{http_code}" "$BASE_URL" | grep -q "200\|302"; then if curl $CURL_OPTS -s -o /dev/null -w "%{http_code}" "$BASE_URL" | grep -q "200\|302"; then
pass "Site still accessible after tests" pass "Site still accessible after tests"
else else
fail "Site not accessible after tests" fail "Site not accessible after tests"
fi fi
# ============================================================================= # =============================================================================
@@ -477,9 +509,9 @@ echo ""
TOTAL=$((TESTS_PASSED + TESTS_FAILED)) TOTAL=$((TESTS_PASSED + TESTS_FAILED))
if [ $TESTS_FAILED -eq 0 ]; then if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}${BOLD}All tests passed!${NC} ($TESTS_PASSED/$TOTAL)" echo -e "${GREEN}${BOLD}All tests passed!${NC} ($TESTS_PASSED/$TOTAL)"
else else
echo -e "${RED}${BOLD}Some tests failed${NC} ($TESTS_PASSED passed, $TESTS_FAILED failed)" echo -e "${RED}${BOLD}Some tests failed${NC} ($TESTS_PASSED passed, $TESTS_FAILED failed)"
fi fi
echo "" echo ""

View File

@@ -56,7 +56,14 @@ def cli(ctx, json_output):
@cli.command() @cli.command()
@click.argument("image", type=click.Path(exists=True)) @click.argument("carrier", type=click.Path(exists=True))
@click.option(
"-r",
"--reference",
required=True,
type=click.Path(exists=True),
help="Reference photo (shared secret)",
)
@click.option("-m", "--message", help="Message to encode") @click.option("-m", "--message", help="Message to encode")
@click.option( @click.option(
"-f", "-f",
@@ -86,18 +93,20 @@ def cli(ctx, json_output):
@click.option("--dry-run", is_flag=True, help="Show capacity usage without encoding") @click.option("--dry-run", is_flag=True, help="Show capacity usage without encoding")
@click.pass_context @click.pass_context
def encode( def encode(
ctx, image, message, file_payload, output, passphrase, pin, compress, algorithm, dry_run ctx, carrier, reference, message, file_payload, output, passphrase, pin, compress, algorithm, dry_run
): ):
""" """
Encode a message or file into an image. Encode a message or file into an image.
Examples: Examples:
stegasoo encode photo.png -m "Secret message" --passphrase --pin stegasoo encode photo.png -r ref.jpg -m "Secret message" --passphrase --pin
stegasoo encode photo.png -f secret.pdf -o encoded.png stegasoo encode photo.png -r ref.jpg -f secret.pdf -o encoded.png
""" """
from PIL import Image from PIL import Image
from .encode import encode as stegasoo_encode
from .encode import encode_file as stegasoo_encode_file
if not message and not file_payload: if not message and not file_payload:
raise click.UsageError("Either --message or --file is required") raise click.UsageError("Either --message or --file is required")
@@ -123,13 +132,14 @@ def encode(
payload_type = "text" payload_type = "text"
# Get image capacity # Get image capacity
with Image.open(image) as img: with Image.open(carrier) as img:
width, height = img.size width, height = img.size
capacity_bytes = (width * height * 3 // 8) - 69 # v3.2.0: corrected overhead capacity_bytes = (width * height * 3 // 8) - 69 # v3.2.0: corrected overhead
if dry_run: if dry_run:
result = { result = {
"image": image, "carrier": carrier,
"reference": reference,
"dimensions": f"{width}x{height}", "dimensions": f"{width}x{height}",
"capacity_bytes": capacity_bytes, "capacity_bytes": capacity_bytes,
"payload_type": payload_type, "payload_type": payload_type,
@@ -142,7 +152,8 @@ def encode(
if ctx.obj.get("json"): if ctx.obj.get("json"):
click.echo(json.dumps(result, indent=2)) click.echo(json.dumps(result, indent=2))
else: else:
click.echo(f"Image: {image} ({width}x{height})") click.echo(f"Carrier: {carrier} ({width}x{height})")
click.echo(f"Reference: {reference}")
click.echo(f"Capacity: {capacity_bytes:,} bytes ({capacity_bytes//1024} KB)") click.echo(f"Capacity: {capacity_bytes:,} bytes ({capacity_bytes//1024} KB)")
click.echo(f"Payload: {payload_size:,} bytes ({payload_type})") click.echo(f"Payload: {payload_size:,} bytes ({payload_type})")
click.echo(f"Compression: {algorithm_name(compression_algo)}") click.echo(f"Compression: {algorithm_name(compression_algo)}")
@@ -150,57 +161,159 @@ def encode(
click.echo(f"Status: {'✓ Fits' if result['fits'] else '✗ Too large'}") click.echo(f"Status: {'✓ Fits' if result['fits'] else '✗ Too large'}")
return return
# Actual encoding would happen here # Read input files
# For now, show what would be done with open(reference, "rb") as f:
output = output or f"{Path(image).stem}_encoded.png" reference_data = f.read()
with open(carrier, "rb") as f:
carrier_data = f.read()
if ctx.obj.get("json"): # Determine output path
click.echo( output = output or f"{Path(carrier).stem}_encoded.png"
json.dumps(
{ try:
"status": "success", if file_payload:
"input": image, # Encode file
"output": output, result = stegasoo_encode_file(
"payload_type": payload_type, filepath=file_payload,
"compression": algorithm_name(compression_algo), reference_photo=reference_data,
}, carrier_image=carrier_data,
indent=2, passphrase=passphrase,
pin=pin,
) )
) else:
else: # Encode message
click.echo(f"✓ Encoded {payload_type} to {output}") result = stegasoo_encode(
click.echo(f" Compression: {algorithm_name(compression_algo)}") message=message,
reference_photo=reference_data,
carrier_image=carrier_data,
passphrase=passphrase,
pin=pin,
)
# Write output
with open(output, "wb") as f:
f.write(result.stego_image)
if ctx.obj.get("json"):
click.echo(
json.dumps(
{
"status": "success",
"carrier": carrier,
"reference": reference,
"output": output,
"payload_type": payload_type,
"compression": algorithm_name(compression_algo),
},
indent=2,
)
)
else:
click.echo(f"✓ Encoded {payload_type} to {output}")
click.echo(f" Reference: {reference}")
click.echo(f" Compression: {algorithm_name(compression_algo)}")
except Exception as e:
if ctx.obj.get("json"):
click.echo(json.dumps({"status": "error", "error": str(e)}, indent=2))
else:
click.echo(f"✗ Encoding failed: {e}", err=True)
raise SystemExit(1)
@cli.command() @cli.command()
@click.argument("image", type=click.Path(exists=True)) @click.argument("image", type=click.Path(exists=True))
@click.option(
"-r",
"--reference",
required=True,
type=click.Path(exists=True),
help="Reference photo (shared secret)",
)
@click.option("--passphrase", prompt=True, hide_input=True, help="Passphrase") @click.option("--passphrase", prompt=True, hide_input=True, help="Passphrase")
@click.option("--pin", prompt=True, hide_input=True, help="PIN code") @click.option("--pin", prompt=True, hide_input=True, help="PIN code")
@click.option("-o", "--output", type=click.Path(), help="Output path for file payloads") @click.option("-o", "--output", type=click.Path(), help="Output path for file payloads")
@click.pass_context @click.pass_context
def decode(ctx, image, passphrase, pin, output): def decode(ctx, image, reference, passphrase, pin, output):
""" """
Decode a message or file from an image. Decode a message or file from an image.
Examples: Examples:
stegasoo decode encoded.png --passphrase --pin stegasoo decode encoded.png -r ref.jpg --passphrase --pin
stegasoo decode encoded.png -o ./extracted/ stegasoo decode encoded.png -r ref.jpg -o ./extracted/
""" """
# Actual decoding would happen here from .decode import decode as stegasoo_decode
result = {
"status": "success",
"image": image,
"payload_type": "text",
"message": "[Decoded message would appear here]",
}
if ctx.obj.get("json"): # Read input files
click.echo(json.dumps(result, indent=2)) with open(image, "rb") as f:
else: stego_data = f.read()
click.echo(f"Decoded from {image}:") with open(reference, "rb") as f:
click.echo(result["message"]) reference_data = f.read()
try:
result = stegasoo_decode(
stego_image=stego_data,
reference_photo=reference_data,
passphrase=passphrase,
pin=pin,
)
if result.is_file:
# File payload
filename = result.filename or "decoded_file"
output_path = Path(output) / filename if output else Path(filename)
# Ensure output directory exists
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "wb") as f:
f.write(result.file_data)
if ctx.obj.get("json"):
click.echo(
json.dumps(
{
"status": "success",
"image": image,
"reference": reference,
"payload_type": "file",
"filename": filename,
"output": str(output_path),
"size": len(result.file_data),
},
indent=2,
)
)
else:
click.echo(f"✓ Extracted file: {output_path}")
click.echo(f" Size: {len(result.file_data):,} bytes")
else:
# Text message
if ctx.obj.get("json"):
click.echo(
json.dumps(
{
"status": "success",
"image": image,
"reference": reference,
"payload_type": "text",
"message": result.message,
},
indent=2,
)
)
else:
click.echo(f"Decoded from {image}:")
click.echo(result.message)
except Exception as e:
if ctx.obj.get("json"):
click.echo(json.dumps({"status": "error", "error": str(e)}, indent=2))
else:
click.echo(f"✗ Decoding failed: {e}", err=True)
raise SystemExit(1)
# ============================================================================= # =============================================================================