@@ -107,17 +107,10 @@ func parseFlakeURLRef(ref string) (parsed FlakeRef, fragment string, err error)
107
107
// [flake:]<flake-id>(/<rev-or-ref>(/rev)?)?
108
108
109
109
parsed .Type = "indirect"
110
-
111
- // "indirect" is parsed as a path, "flake:indirect" is parsed as
112
- // opaque because it has a scheme.
113
- path := refURL .Path
114
- if path == "" {
115
- path , err = url .PathUnescape (refURL .Opaque )
116
- if err != nil {
117
- path = refURL .Opaque
118
- }
110
+ split , err := splitPathOrOpaque (refURL )
111
+ if err != nil {
112
+ return FlakeRef {}, "" , redact .Errorf ("parse flake reference URL path: %v" , err )
119
113
}
120
- split := strings .SplitN (path , "/" , 3 )
121
114
parsed .ID = split [0 ]
122
115
if len (split ) > 1 {
123
116
if isGitHash (split [1 ]) {
@@ -136,7 +129,7 @@ func parseFlakeURLRef(ref string) (parsed FlakeRef, fragment string, err error)
136
129
if refURL .Path == "" {
137
130
parsed .Path , err = url .PathUnescape (refURL .Opaque )
138
131
if err != nil {
139
- parsed . Path = refURL . Opaque
132
+ return FlakeRef {}, "" , err
140
133
}
141
134
} else {
142
135
parsed .Path = refURL .Path
@@ -147,16 +140,20 @@ func parseFlakeURLRef(ref string) (parsed FlakeRef, fragment string, err error)
147
140
} else {
148
141
parsed .Type = "file"
149
142
}
150
- parsed .URL = ref
151
143
parsed .Dir = refURL .Query ().Get ("dir" )
144
+ parsed .URL = refURL .String ()
152
145
case "tarball+http" , "tarball+https" , "tarball+file" :
153
146
parsed .Type = "tarball"
154
147
parsed .Dir = refURL .Query ().Get ("dir" )
155
- parsed .URL = ref [8 :] // remove tarball+
148
+
149
+ refURL .Scheme = refURL .Scheme [8 :] // remove tarball+
150
+ parsed .URL = refURL .String ()
156
151
case "file+http" , "file+https" , "file+file" :
157
152
parsed .Type = "file"
158
153
parsed .Dir = refURL .Query ().Get ("dir" )
159
- parsed .URL = ref [5 :] // remove file+
154
+
155
+ refURL .Scheme = refURL .Scheme [5 :] // remove file+
156
+ parsed .URL = refURL .String ()
160
157
case "git" , "git+http" , "git+https" , "git+ssh" , "git+git" , "git+file" :
161
158
parsed .Type = "git"
162
159
q := refURL .Query ()
@@ -185,17 +182,10 @@ func parseGitHubFlakeRef(refURL *url.URL, parsed *FlakeRef) error {
185
182
// github:<owner>/<repo>(/<rev-or-ref>)?(\?<params>)?
186
183
187
184
parsed .Type = "github"
188
- path := refURL .Path
189
- if path == "" {
190
- var err error
191
- path , err = url .PathUnescape (refURL .Opaque )
192
- if err != nil {
193
- path = refURL .Opaque
194
- }
185
+ split , err := splitPathOrOpaque (refURL )
186
+ if err != nil {
187
+ return err
195
188
}
196
- path = strings .TrimPrefix (path , "/" )
197
-
198
- split := strings .SplitN (path , "/" , 3 )
199
189
parsed .Owner = split [0 ]
200
190
parsed .Repo = split [1 ]
201
191
if len (split ) > 2 {
@@ -359,6 +349,37 @@ func isArchive(path string) bool {
359
349
strings .HasSuffix (path , ".zip" )
360
350
}
361
351
352
+ // splitPathOrOpaque splits a URL path by '/'. If the path is empty, it splits
353
+ // the opaque instead. Splitting happens before unescaping the path or opaque,
354
+ // ensuring that path elements with an encoded '/' (%2F) are not split.
355
+ // For example, "/dir/file%2Fname" becomes the elements "dir" and "file/name".
356
+ func splitPathOrOpaque (u * url.URL ) ([]string , error ) {
357
+ upath := u .EscapedPath ()
358
+ if upath == "" {
359
+ upath = u .Opaque
360
+ }
361
+ upath = strings .TrimSpace (upath )
362
+ if upath == "" {
363
+ return nil , nil
364
+ }
365
+
366
+ // We don't want an empty element if the path is rooted.
367
+ if upath [0 ] == '/' {
368
+ upath = upath [1 :]
369
+ }
370
+ upath = path .Clean (upath )
371
+
372
+ var err error
373
+ split := strings .Split (upath , "/" )
374
+ for i := range split {
375
+ split [i ], err = url .PathUnescape (split [i ])
376
+ if err != nil {
377
+ return nil , err
378
+ }
379
+ }
380
+ return split , nil
381
+ }
382
+
362
383
// buildOpaquePath escapes and joins path elements for a URL flakeref. The
363
384
// resulting path is cleaned according to url.JoinPath.
364
385
func buildOpaquePath (elem ... string ) string {
0 commit comments