#!/system/bin/sh # FlipperDroid WebUI — HTTP server with bridge API endpoints WEBROOT="/data/adb/modules/flipperdroid/webroot" CONFIG_DIR="/data/adb/flipperdroid" CONFIG_FILE="$CONFIG_DIR/config.sh" CMD_FIFO="$CONFIG_DIR/cmd_fifo" RESP_DIR="$CONFIG_DIR/responses" PORT=8089 mkdir -p "$CONFIG_DIR" "$RESP_DIR" source "$CONFIG_FILE" 2>/dev/null PORT="${WEBUI_PORT:-$PORT}" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1"; } # Generate unique request ID req_id() { echo "req_$(date +%s%N | cut -c1-16)_$$"; } # Send command to bridge daemon via FIFO and wait for response bridge_cmd() { local subsystem="$1" local action="$2" local params="${3:-}" local rid=$(req_id) local timeout=10 echo "${rid}|${subsystem}|${action}|${params}" > "$CMD_FIFO" 2>/dev/null # Wait for response file local waited=0 while [ ! -f "$RESP_DIR/${rid}" ] && [ $waited -lt $timeout ]; do sleep 0.1 waited=$((waited + 1)) done if [ -f "$RESP_DIR/${rid}" ]; then cat "$RESP_DIR/${rid}" rm -f "$RESP_DIR/${rid}" else echo "ERR:timeout" fi } # Read config as JSON config_json() { source "$CONFIG_FILE" 2>/dev/null local status=$(cat "$CONFIG_DIR/status" 2>/dev/null || echo "unknown") local conn_type=$(cat "$CONFIG_DIR/conn_type" 2>/dev/null || echo "none") local flipper_dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null || echo "") local flipper_product=$(cat "$CONFIG_DIR/flipper_product" 2>/dev/null || echo "") local flipper_serial=$(cat "$CONFIG_DIR/flipper_serial" 2>/dev/null || echo "") local flipper_version=$(cat "$CONFIG_DIR/flipper_version" 2>/dev/null || echo "") local flipper_caps=$(cat "$CONFIG_DIR/flipper_caps" 2>/dev/null || echo "") cat << EOF { "status": "$status", "conn_type": "$conn_type", "conn_mode": "${CONN_MODE:-auto}", "device": "$flipper_dev", "product": "$flipper_product", "serial": "$flipper_serial", "firmware_version": "$flipper_version", "capabilities": "$flipper_caps", "config": { "auto_connect": ${AUTO_CONNECT:-1}, "baud_rate": ${BAUD_RATE:-115200}, "webui_port": ${WEBUI_PORT:-8089}, "enable_gpio": ${ENABLE_GPIO:-1}, "enable_subghz": ${ENABLE_SUBGHZ:-1}, "enable_rfid": ${ENABLE_RFID:-1}, "enable_nfc": ${ENABLE_NFC:-1}, "enable_ir": ${ENABLE_IR:-1}, "enable_ibutton": ${ENABLE_IBUTTON:-1}, "enable_badusb": ${ENABLE_BADUSB:-0}, "cpu_share": ${CPU_SHARE:-1}, "cpu_share_threads": ${CPU_SHARE_THREADS:-2}, "log_level": ${LOG_LEVEL:-2} } } EOF } # Get recent events as JSON array events_json() { local count="${1:-20}" local events_file="$CONFIG_DIR/events" [ ! -f "$events_file" ] && echo "[]" && return echo -n "[" local first=1 tail -n "$count" "$events_file" | while IFS='|' read -r type data1 data2 ts; do [ $first -eq 1 ] && first=0 || echo -n "," echo -n "{\"type\":\"$type\",\"data\":\"$data1\",\"extra\":\"$data2\",\"timestamp\":$ts}" done echo "]" } ############################# # HTTP request handler ############################# handle_request() { local method="" local path="" local content_length=0 local body="" # Read request line read -r line method=$(echo "$line" | awk '{print $1}') path=$(echo "$line" | awk '{print $2}') # Read headers while read -r header; do header=$(echo "$header" | tr -d '\r') [ -z "$header" ] && break case "$header" in Content-Length:*|content-length:*) content_length=$(echo "$header" | awk '{print $2}') ;; esac done # Read body if POST if [ "$method" = "POST" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then body=$(dd bs=1 count="$content_length" 2>/dev/null) fi # Route local response="" local content_type="application/json" local status="200 OK" case "$method $path" in # Static files "GET /"|"GET /index.html") content_type="text/html" response=$(cat "$WEBROOT/index.html" 2>/dev/null) ;; "GET /css/style.css") content_type="text/css" response=$(cat "$WEBROOT/css/style.css" 2>/dev/null) ;; "GET /js/app.js") content_type="application/javascript" response=$(cat "$WEBROOT/js/app.js" 2>/dev/null) ;; # API: Status & Config "GET /api/status") response=$(config_json) ;; "GET /api/events"*) response=$(events_json 50) ;; "GET /api/log") content_type="text/plain" response=$(tail -n 100 "$CONFIG_DIR/logs/flipperdroid.log" 2>/dev/null) ;; # API: System commands "POST /api/system/ping") response="{\"result\":\"$(bridge_cmd system ping)\"}" ;; "POST /api/system/version") response="{\"result\":\"$(bridge_cmd system version)\"}" ;; "POST /api/system/status") response="{\"result\":\"$(bridge_cmd system status)\"}" ;; # API: GPIO "POST /api/gpio/init") local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*') local mode=$(echo "$body" | grep -o '"mode":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd gpio init "$pin,$mode")\"}" ;; "POST /api/gpio/write") local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*') local val=$(echo "$body" | grep -o '"value":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd gpio write "$pin,$val")\"}" ;; "POST /api/gpio/read") local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd gpio read "$pin")\"}" ;; "POST /api/gpio/pwm") local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*') local freq=$(echo "$body" | grep -o '"freq":[0-9]*' | grep -o '[0-9]*') local duty=$(echo "$body" | grep -o '"duty":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd gpio pwm "$pin,$freq,$duty")\"}" ;; "POST /api/gpio/adc") local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd gpio adc "$pin")\"}" ;; # API: SubGHz "POST /api/subghz/set_freq") local freq=$(echo "$body" | grep -o '"freq":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd subghz set_freq "$freq")\"}" ;; "POST /api/subghz/tx") local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4) response="{\"result\":\"$(bridge_cmd subghz tx "$data")\"}" ;; "POST /api/subghz/rx_start") response="{\"result\":\"$(bridge_cmd subghz rx_start)\"}" ;; "POST /api/subghz/rx_stop") response="{\"result\":\"$(bridge_cmd subghz rx_stop)\"}" ;; "POST /api/subghz/get_rssi") response="{\"result\":\"$(bridge_cmd subghz get_rssi)\"}" ;; "POST /api/subghz/replay") local slot=$(echo "$body" | grep -o '"slot":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd subghz replay "$slot")\"}" ;; # API: RFID "POST /api/rfid/read") response="{\"result\":\"$(bridge_cmd rfid read)\"}" ;; "POST /api/rfid/emulate") local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4) response="{\"result\":\"$(bridge_cmd rfid emulate "$data")\"}" ;; # API: NFC "POST /api/nfc/poll") response="{\"result\":\"$(bridge_cmd nfc poll)\"}" ;; "POST /api/nfc/read_full") response="{\"result\":\"$(bridge_cmd nfc read_full)\"}" ;; "POST /api/nfc/emulate") local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4) local type=$(echo "$body" | grep -o '"type":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd nfc emulate "${data}${type}")\"}" ;; "POST /api/nfc/relay_start") response="{\"result\":\"$(bridge_cmd nfc relay_start)\"}" ;; "POST /api/nfc/relay_stop") response="{\"result\":\"$(bridge_cmd nfc relay_stop)\"}" ;; # API: IR "POST /api/ir/tx") local proto=$(echo "$body" | grep -o '"protocol":[0-9]*' | grep -o '[0-9]*') local addr=$(echo "$body" | grep -o '"address":[0-9]*' | grep -o '[0-9]*') local cmd=$(echo "$body" | grep -o '"command":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd ir tx "$proto,$addr,$cmd")\"}" ;; "POST /api/ir/rx_start") response="{\"result\":\"$(bridge_cmd ir rx_start)\"}" ;; "POST /api/ir/rx_stop") response="{\"result\":\"$(bridge_cmd ir rx_stop)\"}" ;; "POST /api/ir/replay") local slot=$(echo "$body" | grep -o '"slot":[0-9]*' | grep -o '[0-9]*') response="{\"result\":\"$(bridge_cmd ir replay "$slot")\"}" ;; # API: iButton "POST /api/ibutton/read") response="{\"result\":\"$(bridge_cmd ibutton read)\"}" ;; "POST /api/ibutton/emulate") local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4) response="{\"result\":\"$(bridge_cmd ibutton emulate "$data")\"}" ;; # API: BadUSB "POST /api/badusb/start") response="{\"result\":\"$(bridge_cmd badusb start)\"}" ;; "POST /api/badusb/exec") local script=$(echo "$body" | grep -o '"script":"[^"]*"' | cut -d'"' -f4) response="{\"result\":\"$(bridge_cmd badusb exec "$script")\"}" ;; "POST /api/badusb/stop") response="{\"result\":\"$(bridge_cmd badusb stop)\"}" ;; # API: File operations on Flipper SD "POST /api/file/list") local fpath=$(echo "$body" | grep -o '"path":"[^"]*"' | cut -d'"' -f4) response="{\"result\":\"$(bridge_cmd file list "$fpath")\"}" ;; # API: Config update "POST /api/config") # Write config values back local key=$(echo "$body" | grep -o '"key":"[^"]*"' | cut -d'"' -f4) local value=$(echo "$body" | grep -o '"value":"[^"]*"' | cut -d'"' -f4) if [ -n "$key" ] && [ -n "$value" ]; then sed -i "s/^${key}=.*/${key}=${value}/" "$CONFIG_FILE" source "$CONFIG_FILE" response="{\"ok\":true}" else response="{\"ok\":false,\"error\":\"missing key or value\"}" fi ;; # API: Reconnect "POST /api/reconnect") echo "disconnected" > "$CONFIG_DIR/status" response="{\"ok\":true,\"message\":\"reconnecting...\"}" ;; # API: Stealth "GET /api/stealth/status") response=$(/system/bin/fd-stealth status 2>/dev/null || echo '{"error":"stealth not available"}') ;; "POST /api/stealth/apply") /system/bin/fd-stealth apply >> "$CONFIG_DIR/logs/stealth.log" 2>&1 response="{\"ok\":true,\"message\":\"stealth applied\"}" ;; "POST /api/stealth/teardown") /system/bin/fd-stealth teardown >> "$CONFIG_DIR/logs/stealth.log" 2>&1 response="{\"ok\":true,\"message\":\"stealth removed\"}" ;; "POST /api/stealth/hide") /system/bin/fd-stealth hide-dev >> "$CONFIG_DIR/logs/stealth.log" 2>&1 response="{\"ok\":true,\"message\":\"device hidden\"}" ;; "POST /api/stealth/show") /system/bin/fd-stealth show-dev >> "$CONFIG_DIR/logs/stealth.log" 2>&1 response="{\"ok\":true,\"message\":\"device visible\"}" ;; *) status="404 Not Found" response="{\"error\":\"not found\"}" ;; esac # Send HTTP response local content_length=${#response} printf "HTTP/1.1 %s\r\n" "$status" printf "Content-Type: %s\r\n" "$content_type" printf "Content-Length: %d\r\n" "$content_length" printf "Access-Control-Allow-Origin: *\r\n" printf "Connection: close\r\n" printf "\r\n" printf "%s" "$response" } ############################# # Start HTTP server ############################# log "FlipperDroid WebUI starting on port $PORT" # Use busybox nc or toybox for HTTP serving while true; do handle_request | busybox nc -l -p "$PORT" -w 30 2>/dev/null || \ handle_request | toybox nc -l -p "$PORT" -W 30 2>/dev/null || \ handle_request | nc -l -p "$PORT" 2>/dev/null done