@@ -52,27 +52,23 @@ defmodule Opsm.Verified.Url do
5252 if String . contains? ( url_string , "::1" ) do
5353 { :error , :blocked_host }
5454 else
55- case Proven.SafeUrl . parse ( url_string ) do
56- { :ok , parsed } ->
57- scheme = parsed . scheme
58- host = parsed . host
59-
55+ case URI . parse ( url_string ) do
56+ % URI { scheme: scheme , host: host } when scheme != nil and host != nil ->
6057 cond do
6158 scheme not in @ allowed_schemes ->
6259 { :error , { :invalid_scheme , scheme } }
6360
6461 host in @ blocked_hosts ->
6562 { :error , :blocked_host }
6663
67- ( Proven.SafeNetwork . valid_ipv4? ( host ) and
68- ( Proven.SafeNetwork . private? ( host ) or Proven.SafeNetwork . loopback? ( host ) ) ) or
69- String . starts_with? ( host , "169.254." ) ->
64+ is_private_or_loopback_ip? ( host ) or String . starts_with? ( host , "169.254." ) ->
7065 { :error , :blocked_host }
7166
7267 true ->
68+ parsed = URI . parse ( url_string )
7369 validated = % __MODULE__ {
74- scheme: scheme ,
75- host: host ,
70+ scheme: parsed . scheme ,
71+ host: parsed . host ,
7672 port: parsed . port ,
7773 path: parsed . path || "/" ,
7874 query: parsed . query ,
@@ -82,7 +78,7 @@ defmodule Opsm.Verified.Url do
8278 { :ok , validated }
8379 end
8480
85- { :error , _ } ->
81+ _ ->
8682 { :error , :invalid_url }
8783 end
8884 end
@@ -98,6 +94,44 @@ defmodule Opsm.Verified.Url do
9894 url . original
9995 end
10096
97+ # Helper function to check for private/loopback IP addresses
98+ defp is_private_or_loopback_ip? ( host ) do
99+ case parse_ipv4 ( host ) do
100+ { :ok , { a , b , _c , _d } } ->
101+ # Loopback: 127.0.0.0/8
102+ a == 127 or
103+ # Private: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
104+ a == 10 or
105+ ( a == 172 and b >= 16 and b <= 31 ) or
106+ ( a == 192 and b == 168 )
107+
108+ :error ->
109+ false
110+ end
111+ end
112+
113+ # Parse IPv4 address
114+ defp parse_ipv4 ( str ) do
115+ case String . split ( str , "." ) do
116+ [ a , b , c , d ] ->
117+ with { a_int , "" } <- Integer . parse ( a ) ,
118+ { b_int , "" } <- Integer . parse ( b ) ,
119+ { c_int , "" } <- Integer . parse ( c ) ,
120+ { d_int , "" } <- Integer . parse ( d ) ,
121+ true <- a_int >= 0 and a_int <= 255 ,
122+ true <- b_int >= 0 and b_int <= 255 ,
123+ true <- c_int >= 0 and c_int <= 255 ,
124+ true <- d_int >= 0 and d_int <= 255 do
125+ { :ok , { a_int , b_int , c_int , d_int } }
126+ else
127+ _ -> :error
128+ end
129+
130+ _ ->
131+ :error
132+ end
133+ end
134+
101135end
102136
103137defmodule Opsm.Verified.Json do
@@ -123,12 +157,25 @@ defmodule Opsm.Verified.Json do
123157 """
124158 @ spec decode ( String . t ( ) ) :: { :ok , map ( ) | list ( ) } | { :error , term ( ) }
125159 def decode ( json_string ) when is_binary ( json_string ) do
126- case Proven.SafeJson . parse ( json_string , max_depth: @ max_depth , max_size: @ max_size ) do
127- { :ok , data } -> { :ok , data }
128- { :error , :payload_too_large } -> { :error , :payload_too_large }
129- { :error , :max_depth_exceeded } -> { :error , :nesting_too_deep }
130- { :error , :invalid_json } -> { :error , { :json_decode_error , :invalid_json } }
131- { :error , reason } -> { :error , reason }
160+ # Check size limit
161+ if byte_size ( json_string ) > @ max_size do
162+ { :error , :payload_too_large }
163+ else
164+ case Jason . decode ( json_string ) do
165+ { :ok , data } ->
166+ # Check depth after parsing
167+ if check_depth ( data , @ max_depth ) do
168+ { :ok , data }
169+ else
170+ { :error , :nesting_too_deep }
171+ end
172+
173+ { :error , % Jason.DecodeError { } } ->
174+ { :error , { :json_decode_error , :invalid_json } }
175+
176+ { :error , reason } ->
177+ { :error , reason }
178+ end
132179 end
133180 end
134181
@@ -139,13 +186,32 @@ defmodule Opsm.Verified.Json do
139186 """
140187 @ spec encode ( term ( ) ) :: { :ok , String . t ( ) } | { :error , term ( ) }
141188 def encode ( data ) do
142- case Proven.SafeJson . encode ( data , max_size: @ max_size ) do
143- { :ok , json } -> { :ok , json }
144- { :error , :payload_too_large } -> { :error , :payload_too_large }
145- { :error , reason } -> { :error , { :json_encode_error , reason } }
189+ case Jason . encode ( data ) do
190+ { :ok , json } ->
191+ if byte_size ( json ) > @ max_size do
192+ { :error , :payload_too_large }
193+ else
194+ { :ok , json }
195+ end
196+
197+ { :error , % Jason.EncodeError { } = error } ->
198+ { :error , { :json_encode_error , error . message } }
199+
200+ { :error , reason } ->
201+ { :error , { :json_encode_error , reason } }
146202 end
147203 end
148204
205+ # Check nesting depth recursively
206+ defp check_depth ( _data , 0 ) , do: false
207+ defp check_depth ( data , depth ) when is_map ( data ) do
208+ Enum . all? ( data , fn { _k , v } -> check_depth ( v , depth - 1 ) end )
209+ end
210+ defp check_depth ( data , depth ) when is_list ( data ) do
211+ Enum . all? ( data , fn v -> check_depth ( v , depth - 1 ) end )
212+ end
213+ defp check_depth ( _data , _depth ) , do: true
214+
149215end
150216
151217defmodule Opsm.Verified.Result do
0 commit comments