@@ -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 {
@@ -280,7 +270,7 @@ func (f FlakeRef) String() string {
280
270
}
281
271
url := & url.URL {
282
272
Scheme : "github" ,
283
- Opaque : buildOpaquePath (f .Owner , f .Repo , f .Rev , f .Ref ),
273
+ Opaque : buildEscapedPath (f .Owner , f .Repo , f .Rev , f .Ref ),
284
274
RawQuery : buildQueryString ("host" , f .Host , "dir" , f .Dir ),
285
275
}
286
276
return url .String ()
@@ -290,7 +280,7 @@ func (f FlakeRef) String() string {
290
280
}
291
281
url := & url.URL {
292
282
Scheme : "flake" ,
293
- Opaque : buildOpaquePath (f .ID , f .Ref , f .Rev ),
283
+ Opaque : buildEscapedPath (f .ID , f .Ref , f .Rev ),
294
284
RawQuery : buildQueryString ("dir" , f .Dir ),
295
285
}
296
286
return url .String ()
@@ -301,7 +291,7 @@ func (f FlakeRef) String() string {
301
291
f .Path = path .Clean (f .Path )
302
292
url := & url.URL {
303
293
Scheme : "path" ,
304
- Opaque : buildOpaquePath (strings .Split (f .Path , "/" )... ),
294
+ Opaque : buildEscapedPath (strings .Split (f .Path , "/" )... ),
305
295
}
306
296
307
297
// Add the / prefix back if strings.Split removed it.
@@ -359,9 +349,40 @@ func isArchive(path string) bool {
359
349
strings .HasSuffix (path , ".zip" )
360
350
}
361
351
362
- // buildOpaquePath escapes and joins path elements for a URL flakeref. The
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
+
383
+ // buildEscapedPath escapes and joins path elements for a URL flakeref. The
363
384
// resulting path is cleaned according to url.JoinPath.
364
- func buildOpaquePath (elem ... string ) string {
385
+ func buildEscapedPath (elem ... string ) string {
365
386
for i := range elem {
366
387
elem [i ] = url .PathEscape (elem [i ])
367
388
}
0 commit comments