@@ -55,6 +55,26 @@ pub fn expand_path(user: Option<&expand_path::ForUser>, path: &BStr) -> Result<P
5555 } )
5656}
5757
58+ /// Classification of a portion of a URL by whether it is *syntactically* safe to pass as an argument to a command-line program.
59+ ///
60+ /// Various parts of URLs can be specified to begin with `-`. If they are used as options to a command-line application
61+ /// such as an SSH client, they will be treated as options rather than as non-option arguments as the developer intended.
62+ /// This is a security risk, because URLs are not always trusted and can often be composed or influenced by an attacker.
63+ /// See <https://secure.phabricator.com/T12961> for details.
64+ ///
65+ /// # Security Warning
66+ ///
67+ /// This type only expresses known *syntactic* risk. It does not cover other risks, such as passing a personal access
68+ /// token as a username rather than a password in an application that logs usernames.
69+ enum ArgumentSafety < ' a , T > {
70+ /// May be safe. There is nothing to pass, so there is nothing dangerous.
71+ Absent ,
72+ /// May be safe. The argument does not begin with a `-` and so will not be confused as an option.
73+ Usable ( & ' a T ) ,
74+ /// Dangerous! Begins with `-` and could be treated as an option. Use the value in error messages only.
75+ Dangerous ( & ' a T ) ,
76+ }
77+
5878/// A URL with support for specialized git related capabilities.
5979///
6080/// Additionally there is support for [deserialization](Url::from_bytes()) and [serialization](Url::to_bstring()).
@@ -85,7 +105,7 @@ pub struct Url {
85105 pub port : Option < u16 > ,
86106 /// The path portion of the URL, usually the location of the git repository.
87107 ///
88- /// # Security- Warning
108+ /// # Security Warning
89109 ///
90110 /// URLs allow paths to start with `-` which makes it possible to mask command-line arguments as path which then leads to
91111 /// the invocation of programs from an attacker controlled URL. See <https://secure.phabricator.com/T12961> for details.
@@ -164,9 +184,9 @@ impl Url {
164184
165185/// Access
166186impl Url {
167- /// Return the user mentioned in the URL, if present.
187+ /// Return the username mentioned in the URL, if present.
168188 ///
169- /// # Security- Warning
189+ /// # Security Warning
170190 ///
171191 /// URLs allow usernames to start with `-` which makes it possible to mask command-line arguments as username which then leads to
172192 /// the invocation of programs from an attacker controlled URL. See <https://secure.phabricator.com/T12961> for details.
@@ -176,19 +196,28 @@ impl Url {
176196 self . user . as_deref ( )
177197 }
178198
179- /// Return the user from this URL if present *and* if it can't be mistaken for a command-line argument.
199+ /// Classify the username of this URL by whether it is safe to pass as a command-line argument.
180200 ///
181- /// Use this method if the user or a portion of the URL that begins with it will be passed to a command-line application.
201+ /// Use this method instead of [Self::user()] if the host is going to be passed to a command-line application.
202+ /// If the unsafe and absent cases need not be distinguished, [Self::user_argument_safe()] may also be used.
203+ pub fn user_as_argument ( & self ) -> ArgumentSafety < str > {
204+ match self . user ( ) {
205+ Some ( user) if looks_like_command_line_option ( user. as_bytes ( ) ) => ArgumentSafety :: Dangerous ( user) ,
206+ Some ( user) => ArgumentSafety :: Usable ( user) ,
207+ None => ArgumentSafety :: Absent ,
208+ }
209+ }
210+
211+ /// Return the username of this URL if present *and* if it can't be mistaken for a command-line argument.
212+ ///
213+ /// Use this method or [Self::user_as_argument()] instead of [Self::user()] if the host is going to be
214+ /// passed to a command-line application. Prefer [Self::user_as_argument()] unless the unsafe and absent
215+ /// cases need not be distinguished from each other.
182216 pub fn user_argument_safe ( & self ) -> Option < & str > {
183- // FIXME: A return value of None from this method, or host_argument_safe(), is ambiguous: the user (or host) is
184- // either present but unsafe, or absent. Furthermore, in practice the value is usually needed even if unsafe,
185- // in order to report it in an error message. In gix-transport, the ambiguity makes it easy to write a new bug
186- // while using this interface for user_argument_safe(). In contrast, in host_argument_safe(), the ambiguity is
187- // much less of a problem, because the host is expected to be present. Yet the host() method must still be
188- // called when handling the None case, to include it in the error. If possible, both methods should be replaced
189- // by methods with a richer return type (a new enum). If not, the ambiguity should be prominently documented.
190- self . user ( )
191- . filter ( |user| !looks_like_command_line_option ( user. as_bytes ( ) ) )
217+ match self . user_as_argument ( ) {
218+ ArgumentSafety :: Usable ( user) => Some ( user) ,
219+ _ => None ,
220+ }
192221 }
193222
194223 /// Return the password mentioned in the url, if present.
@@ -198,28 +227,63 @@ impl Url {
198227
199228 /// Return the host mentioned in the URL, if present.
200229 ///
201- /// # Security- Warning
230+ /// # Security Warning
202231 ///
203232 /// URLs allow hosts to start with `-` which makes it possible to mask command-line arguments as host which then leads to
204233 /// the invocation of programs from an attacker controlled URL. See <https://secure.phabricator.com/T12961> for details.
205234 ///
206- /// If this value is ever going to be passed to a command-line application, call [Self::host_argument_safe()] instead.
235+ /// If this value is ever going to be passed to a command-line application, call [Self::host_as_argument()]
236+ /// or [Self::host_argument_safe()] instead.
207237 pub fn host ( & self ) -> Option < & str > {
208238 self . host . as_deref ( )
209239 }
210240
241+ /// Classify the host of this URL by whether it is safe to pass as a command-line argument.
242+ ///
243+ /// Use this method instead of [Self::host()] if the host is going to be passed to a command-line application.
244+ /// If the unsafe and absent cases need not be distinguished, [Self::host_argument_safe()] may also be used.
245+ pub fn host_as_argument ( & self ) -> ArgumentSafety < str > {
246+ match self . host ( ) {
247+ Some ( host) if looks_like_command_line_option ( host. as_bytes ( ) ) => ArgumentSafety :: Dangerous ( host) ,
248+ Some ( host) => ArgumentSafety :: Usable ( host) ,
249+ None => ArgumentSafety :: Absent ,
250+ }
251+ }
252+
211253 /// Return the host of this URL if present *and* if it can't be mistaken for a command-line argument.
212254 ///
213- /// Use this method if the host is going to be passed to a command-line application.
255+ /// Use this method or [Self::host_as_argument()] instead of [Self::host()] if the host is going to be
256+ /// passed to a command-line application. Prefer [Self::host_as_argument()] unless the unsafe and absent
257+ /// cases need not be distinguished from each other.
214258 pub fn host_argument_safe ( & self ) -> Option < & str > {
215- self . host ( )
216- . filter ( |host| !looks_like_command_line_option ( host. as_bytes ( ) ) )
259+ match self . host_as_argument ( ) {
260+ ArgumentSafety :: Usable ( host) => Some ( host) ,
261+ _ => None ,
262+ }
263+ }
264+
265+ /// Classify the path of this URL by whether it is safe to pass as a command-line argument.
266+ /// Note that it always begins with a slash, which is ignored for this classification.
267+ ///
268+ /// Use this method or [Self::path_argument_safe()] instead of [Self::path] if the host is going to be
269+ /// passed to a command-line application, unless it is certain that the leading `/` will always be included.
270+ ///
271+ /// This method never returns an [ArgumentSafety::Absent].
272+ pub fn path_as_argument ( & self ) -> ArgumentSafety < BStr > {
273+ match self . path_argument_safe ( ) {
274+ Some ( path) => ArgumentSafety :: Usable ( path) ,
275+ None => ArgumentSafety :: Dangerous ( self . path . as_ref ( ) ) ,
276+ }
217277 }
218278
219279 /// Return the path of this URL *if* it can't be mistaken for a command-line argument.
220280 /// Note that it always begins with a slash, which is ignored for this comparison.
221281 ///
222- /// Use this method if the path is going to be passed to a command-line application.
282+ /// Use this method instead of accessing [Self::path] directly if the path is going to be passed to a
283+ /// command-line application, unless it is certain that the leading `/` will always be included.
284+ ///
285+ /// The result of this method is unambiguous because [Self::path] is not an option type, but
286+ /// [Self::path_as_argument()] may also safely be used.
223287 pub fn path_argument_safe ( & self ) -> Option < & BStr > {
224288 self . path
225289 . get ( 1 ..)
0 commit comments