@@ -34,15 +34,22 @@ defmodule Forge.Document.Path do
3434
3535 def from_uri ( % URI { scheme: @ file_scheme , path: path , authority: authority } )
3636 when path != "" and authority not in [ "" , nil ] do
37- # UNC path
38- convert_separators_to_native ( "//#{ URI . decode ( authority ) } #{ URI . decode ( path ) } " )
37+ decoded_authority = URI . decode ( authority )
38+ decoded_path = URI . decode ( path )
39+
40+ # Check if authority is a Windows drive letter (e.g., "d:")
41+ if Forge.OS . windows? ( ) and String . match? ( decoded_authority , ~r/ ^[a-zA-Z]:$/ ) do
42+ convert_separators_to_native ( "#{ decoded_authority } #{ decoded_path } " )
43+ else
44+ convert_separators_to_native ( "//#{ decoded_authority } #{ decoded_path } " )
45+ end
3946 end
4047
4148 def from_uri ( % URI { scheme: @ file_scheme , path: path } ) do
4249 decoded_path = URI . decode ( path )
4350
4451 path =
45- if windows? ( ) and String . match? ( decoded_path , ~r/ ^\/ [a-zA-Z]:/ ) do
52+ if Forge.OS . windows? ( ) and String . match? ( decoded_path , ~r/ ^\/ [a-zA-Z]:/ ) do
4653 # Windows drive letter path
4754 # drop leading `/` and downcase drive letter
4855 << "/" , letter :: binary - size ( 1 ) , path_rest :: binary >> = decoded_path
@@ -75,9 +82,31 @@ defmodule Forge.Document.Path do
7582 Converts a path into a URI
7683 """
7784 def to_uri ( path ) do
85+ expanded_path = Path . expand ( path )
86+
87+ # Validate Windows paths - reject malformed drive paths
88+ if Forge.OS . windows? ( ) and invalid_windows_path? ( path , expanded_path ) do
89+ nil
90+ else
91+ do_to_uri ( expanded_path )
92+ end
93+ end
94+
95+ defp invalid_windows_path? ( original_path , _expanded_path ) do
96+ # Check for malformed drive paths like "c:filename" without directory separator
97+ case original_path do
98+ # Pattern like "c:something" without slash after colon
99+ << drive_letter , ":" , rest :: binary >> when drive_letter in ?a .. ?z or drive_letter in ?A .. ?Z ->
100+ # If rest doesn't start with / or \, it's malformed
101+ not ( String . starts_with? ( rest , "/" ) or String . starts_with? ( rest , "\\ " ) )
102+ _ ->
103+ false
104+ end
105+ end
106+
107+ defp do_to_uri ( path ) do
78108 path =
79109 path
80- |> Path . expand ( )
81110 |> convert_separators_to_universal ( )
82111
83112 { authority , path } =
@@ -117,7 +146,7 @@ defmodule Forge.Document.Path do
117146 end
118147
119148 defp convert_separators_to_native ( path ) do
120- if windows? ( ) do
149+ if Forge.OS . windows? ( ) do
121150 # convert path separators from URI to Windows
122151 String . replace ( path , ~r/ \/ / , "\\ " )
123152 else
@@ -126,23 +155,11 @@ defmodule Forge.Document.Path do
126155 end
127156
128157 defp convert_separators_to_universal ( path ) do
129- if windows? ( ) do
158+ if Forge.OS . windows? ( ) do
130159 # convert path separators from Windows to URI
131160 String . replace ( path , ~r/ \\ / , "/" )
132161 else
133162 path
134163 end
135164 end
136-
137- defp windows? do
138- case os_type ( ) do
139- { :win32 , _ } -> true
140- _ -> false
141- end
142- end
143-
144- # this is here to be mocked in tests
145- defp os_type do
146- :os . type ( )
147- end
148165end
0 commit comments