From d234c313caff20c3f0b1975a1352a216d99f4ca4 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sun, 22 Mar 2026 17:36:09 -0500 Subject: [PATCH] fix: harden mobile channel numeric params --- lib/aprsme/packets/prepared_queries.ex | 5 +++- lib/aprsme_web/channels/mobile_channel.ex | 19 +++++++++--- test/aprsme/streaming_packets_pubsub_test.exs | 6 ++-- .../channels/mobile_channel_test.exs | 29 +++++++++++++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/aprsme/packets/prepared_queries.ex b/lib/aprsme/packets/prepared_queries.ex index bbff881..a07af60 100644 --- a/lib/aprsme/packets/prepared_queries.ex +++ b/lib/aprsme/packets/prepared_queries.ex @@ -258,7 +258,10 @@ defmodule Aprsme.Packets.PreparedQueries do # Convert miles to meters for PostGIS (1 mile = 1609.34 meters) radius_meters = radius_miles * 1609.34 - cutoff_time = DateTime.add(DateTime.utc_now(), -hours * 3600, :second) + cutoff_time = + DateTime.utc_now() + |> DateTime.truncate(:second) + |> DateTime.add(-hours * 3600, :second) # Build point for spatial query point = %Geo.Point{coordinates: {lon, lat}, srid: 4326} diff --git a/lib/aprsme_web/channels/mobile_channel.ex b/lib/aprsme_web/channels/mobile_channel.ex index ba86c08..8513f13 100644 --- a/lib/aprsme_web/channels/mobile_channel.ex +++ b/lib/aprsme_web/channels/mobile_channel.ex @@ -163,7 +163,7 @@ defmodule AprsmeWeb.MobileChannel do # Trim whitespace and control characters from query query = String.trim(query) - limit = Map.get(payload, "limit", 50) + limit = payload |> Map.get("limit", 50) |> ensure_integer(50) limit = min(limit, 500) results = search_callsign(query, limit) @@ -175,7 +175,7 @@ defmodule AprsmeWeb.MobileChannel do def handle_in("subscribe_callsign", %{"callsign" => callsign} = payload, socket) do Logger.info("Mobile websocket received subscribe_callsign: #{inspect(payload)}") - hours_back = Map.get(payload, "hours_back", 24) + hours_back = payload |> Map.get("hours_back", 24) |> ensure_integer(24) # Max 1 week hours_back = min(hours_back, 168) @@ -278,6 +278,17 @@ defmodule AprsmeWeb.MobileChannel do defp ensure_float(value) when is_binary(value), do: String.to_float(value) defp ensure_float(value), do: value + defp ensure_integer(value, _default) when is_integer(value), do: value + + defp ensure_integer(value, default) when is_binary(value) do + case Integer.parse(String.trim(value)) do + {int, _} -> int + :error -> default + end + end + + defp ensure_integer(_value, default), do: default + defp build_mobile_packet(packet) do # Extract coordinates {lat, lon, _data_extended} = CoordinateUtils.get_coordinates(packet) @@ -335,8 +346,8 @@ defmodule AprsmeWeb.MobileChannel do defp load_historical_packets(socket, bounds, payload) do # Get optional parameters from payload - limit = Map.get(payload, "limit", 1000) - hours_back = Map.get(payload, "hours_back", 1) + limit = payload |> Map.get("limit", 1000) |> ensure_integer(1000) + hours_back = payload |> Map.get("hours_back", 1) |> ensure_integer(1) # Ensure reasonable limits limit = min(limit, 5000) diff --git a/test/aprsme/streaming_packets_pubsub_test.exs b/test/aprsme/streaming_packets_pubsub_test.exs index d1a1784..b6bc4c4 100644 --- a/test/aprsme/streaming_packets_pubsub_test.exs +++ b/test/aprsme/streaming_packets_pubsub_test.exs @@ -145,12 +145,12 @@ defmodule Aprsme.StreamingPacketsPubSubTest do pid = Process.whereis(StreamingPacketsPubSub) if pid do - GenServer.stop(pid) + :erlang.unregister(StreamingPacketsPubSub) end on_exit(fn -> - if !Process.whereis(StreamingPacketsPubSub) do - start_supervised!({StreamingPacketsPubSub, []}) + if pid && !Process.whereis(StreamingPacketsPubSub) do + true = Process.register(pid, StreamingPacketsPubSub) end end) diff --git a/test/aprsme_web/channels/mobile_channel_test.exs b/test/aprsme_web/channels/mobile_channel_test.exs index 6c7af4a..1ff2a2e 100644 --- a/test/aprsme_web/channels/mobile_channel_test.exs +++ b/test/aprsme_web/channels/mobile_channel_test.exs @@ -261,6 +261,13 @@ defmodule AprsmeWeb.MobileChannelTest do assert is_list(results) end + test "accepts string limit values", %{socket: socket} do + ref = push(socket, "search_callsign", %{"query" => "W5ISP", "limit" => "1"}) + + assert_reply ref, :ok, %{count: count} + assert count <= 1 + end + test "normalizes callsign to uppercase", %{socket: socket} do ref = push(socket, "search_callsign", %{"query" => "w5isp"}) @@ -291,6 +298,12 @@ defmodule AprsmeWeb.MobileChannelTest do # Should not crash even with hours_back > 168 end + test "accepts string hours_back values", %{socket: socket} do + ref = push(socket, "subscribe_callsign", %{"callsign" => "W5ISP-9", "hours_back" => "24"}) + + assert_reply ref, :ok, %{callsign: "W5ISP-9"} + end + test "normalizes callsign to uppercase", %{socket: socket} do ref = push(socket, "subscribe_callsign", %{"callsign" => "w5isp-9"}) @@ -499,4 +512,20 @@ defmodule AprsmeWeb.MobileChannelTest do refute Map.has_key?(pushed_packet, :path) end end + + describe "subscribe_bounds with historical options" do + test "accepts string limit and hours_back values", %{socket: socket} do + ref = + push(socket, "subscribe_bounds", %{ + "north" => 33.2, + "south" => 33.0, + "east" => -96.0, + "west" => -96.2, + "limit" => "10", + "hours_back" => "2" + }) + + assert_reply ref, :ok, %{message: "Subscribed to packet stream"} + end + end end