Skip to content

Commit 830cb8c

Browse files
tdesveauxKN4CK3R
andcommitted
Fix NuGet Package API for $filter with Id equality (go-gitea#31188)
Fixes issue when running `choco info pkgname` where `pkgname` is also a substring of another package Id. Relates to go-gitea#31168 --- This might fix the issue linked, but I'd like to test it with more choco commands before closing the issue in case I find other problems if that's ok. --------- Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
1 parent 331c32f commit 830cb8c

File tree

2 files changed

+115
-35
lines changed

2 files changed

+115
-35
lines changed

routers/api/packages/nuget/nuget.go

+29-19
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,34 @@ func FeedCapabilityResource(ctx *context.Context) {
9696
xmlResponse(ctx, http.StatusOK, Metadata)
9797
}
9898

99-
var searchTermExtract = regexp.MustCompile(`'([^']+)'`)
99+
var (
100+
searchTermExtract = regexp.MustCompile(`'([^']+)'`)
101+
searchTermExact = regexp.MustCompile(`\s+eq\s+'`)
102+
)
100103

101-
func getSearchTerm(ctx *context.Context) string {
104+
func getSearchTerm(ctx *context.Context) packages_model.SearchValue {
102105
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
103-
if searchTerm == "" {
104-
// $filter contains a query like:
105-
// (((Id ne null) and substringof('microsoft',tolower(Id)))
106-
// We don't support these queries, just extract the search term.
107-
match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter"))
108-
if len(match) == 2 {
109-
searchTerm = strings.TrimSpace(match[1])
106+
if searchTerm != "" {
107+
return packages_model.SearchValue{
108+
Value: searchTerm,
109+
ExactMatch: false,
110+
}
111+
}
112+
113+
// $filter contains a query like:
114+
// (((Id ne null) and substringof('microsoft',tolower(Id)))
115+
// https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5
116+
// We don't support these queries, just extract the search term.
117+
filter := ctx.FormTrim("$filter")
118+
match := searchTermExtract.FindStringSubmatch(filter)
119+
if len(match) == 2 {
120+
return packages_model.SearchValue{
121+
Value: strings.TrimSpace(match[1]),
122+
ExactMatch: searchTermExact.MatchString(filter),
110123
}
111124
}
112-
return searchTerm
125+
126+
return packages_model.SearchValue{}
113127
}
114128

115129
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
@@ -118,11 +132,9 @@ func SearchServiceV2(ctx *context.Context) {
118132
paginator := db.NewAbsoluteListOptions(skip, take)
119133

120134
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
121-
OwnerID: ctx.Package.Owner.ID,
122-
Type: packages_model.TypeNuGet,
123-
Name: packages_model.SearchValue{
124-
Value: getSearchTerm(ctx),
125-
},
135+
OwnerID: ctx.Package.Owner.ID,
136+
Type: packages_model.TypeNuGet,
137+
Name: getSearchTerm(ctx),
126138
IsInternal: optional.Some(false),
127139
Paginator: paginator,
128140
})
@@ -169,10 +181,8 @@ func SearchServiceV2(ctx *context.Context) {
169181
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
170182
func SearchServiceV2Count(ctx *context.Context) {
171183
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
172-
OwnerID: ctx.Package.Owner.ID,
173-
Name: packages_model.SearchValue{
174-
Value: getSearchTerm(ctx),
175-
},
184+
OwnerID: ctx.Package.Owner.ID,
185+
Name: getSearchTerm(ctx),
176186
IsInternal: optional.Some(false),
177187
})
178188
if err != nil {

tests/integration/api_packages_nuget_test.go

+86-16
Original file line numberDiff line numberDiff line change
@@ -429,22 +429,33 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
429429

430430
t.Run("SearchService", func(t *testing.T) {
431431
cases := []struct {
432-
Query string
433-
Skip int
434-
Take int
435-
ExpectedTotal int64
436-
ExpectedResults int
432+
Query string
433+
Skip int
434+
Take int
435+
ExpectedTotal int64
436+
ExpectedResults int
437+
ExpectedExactMatch bool
437438
}{
438-
{"", 0, 0, 1, 1},
439-
{"", 0, 10, 1, 1},
440-
{"gitea", 0, 10, 0, 0},
441-
{"test", 0, 10, 1, 1},
442-
{"test", 1, 10, 1, 0},
439+
{"", 0, 0, 4, 4, false},
440+
{"", 0, 10, 4, 4, false},
441+
{"gitea", 0, 10, 0, 0, false},
442+
{"test", 0, 10, 1, 1, false},
443+
{"test", 1, 10, 1, 0, false},
444+
{"almost.similar", 0, 0, 3, 3, true},
443445
}
444446

445-
req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99")).
446-
AddBasicAuth(user.Name)
447-
MakeRequest(t, req, http.StatusCreated)
447+
fakePackages := []string{
448+
packageName,
449+
"almost.similar.dependency",
450+
"almost.similar",
451+
"almost.similar.dependant",
452+
}
453+
454+
for _, fakePackageName := range fakePackages {
455+
req := NewRequestWithBody(t, "PUT", url, createPackage(fakePackageName, "1.0.99")).
456+
AddBasicAuth(user.Name)
457+
MakeRequest(t, req, http.StatusCreated)
458+
}
448459

449460
t.Run("v2", func(t *testing.T) {
450461
t.Run("Search()", func(t *testing.T) {
@@ -491,6 +502,63 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
491502
}
492503
})
493504

505+
t.Run("Packages()", func(t *testing.T) {
506+
defer tests.PrintCurrentTest(t)()
507+
508+
t.Run("substringof", func(t *testing.T) {
509+
defer tests.PrintCurrentTest(t)()
510+
511+
for i, c := range cases {
512+
req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
513+
AddBasicAuth(user.Name)
514+
resp := MakeRequest(t, req, http.StatusOK)
515+
516+
var result FeedResponse
517+
decodeXML(t, resp, &result)
518+
519+
assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
520+
assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
521+
522+
req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
523+
AddBasicAuth(user.Name)
524+
resp = MakeRequest(t, req, http.StatusOK)
525+
526+
assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
527+
}
528+
})
529+
530+
t.Run("IdEq", func(t *testing.T) {
531+
defer tests.PrintCurrentTest(t)()
532+
533+
for i, c := range cases {
534+
if c.Query == "" {
535+
// Ignore the `tolower(Id) eq ''` as it's unlikely to happen
536+
continue
537+
}
538+
req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
539+
AddBasicAuth(user.Name)
540+
resp := MakeRequest(t, req, http.StatusOK)
541+
542+
var result FeedResponse
543+
decodeXML(t, resp, &result)
544+
545+
expectedCount := 0
546+
if c.ExpectedExactMatch {
547+
expectedCount = 1
548+
}
549+
550+
assert.Equal(t, int64(expectedCount), result.Count, "case %d: unexpected total hits", i)
551+
assert.Len(t, result.Entries, expectedCount, "case %d: unexpected result count", i)
552+
553+
req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
554+
AddBasicAuth(user.Name)
555+
resp = MakeRequest(t, req, http.StatusOK)
556+
557+
assert.Equal(t, strconv.FormatInt(int64(expectedCount), 10), resp.Body.String(), "case %d: unexpected total hits", i)
558+
}
559+
})
560+
})
561+
494562
t.Run("Next", func(t *testing.T) {
495563
req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)).
496564
AddBasicAuth(user.Name)
@@ -548,9 +616,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
548616
})
549617
})
550618

551-
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99")).
552-
AddBasicAuth(user.Name)
553-
MakeRequest(t, req, http.StatusNoContent)
619+
for _, fakePackageName := range fakePackages {
620+
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, fakePackageName, "1.0.99")).
621+
AddBasicAuth(user.Name)
622+
MakeRequest(t, req, http.StatusNoContent)
623+
}
554624
})
555625

556626
t.Run("RegistrationService", func(t *testing.T) {

0 commit comments

Comments
 (0)