Skip to content

Commit c62f191

Browse files
authored
Support versioned packages in allowList/banList (#1205)
1 parent c783819 commit c62f191

File tree

5 files changed

+86
-86
lines changed

5 files changed

+86
-86
lines changed

config.example.jsonc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,7 @@
107107
// The list to only allow some packages or scopes, default allow all.
108108
"allowList": {
109109
"packages": ["@scope_name/package_name"],
110-
"scopes": [{
111-
"name": "@scope_name"
112-
}]
110+
"scopes": ["@scope_name"]
113111
},
114112

115113
// The list to ban some packages or scopes, default no ban.

server/build_resolver.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,18 @@ func (ctx *BuildContext) resolveEntry(esm EsmPath) (entry BuildEntry) {
314314
}
315315
*/
316316
exportEntry = ctx.resolveConditionExportEntry(obj, pkgJson.Type)
317+
} else if arr, ok := v.([]any); ok {
318+
/**
319+
exports: {
320+
".": ["./cjs/index.js", "./esm/index.js"]
321+
}
322+
*/
323+
a0 := arr[0]
324+
if s, ok := a0.(string); ok {
325+
exportEntry.update(s, pkgJson.Type == "module")
326+
} else if obj, ok := a0.(npm.JSONObject); ok {
327+
exportEntry = ctx.resolveConditionExportEntry(obj, pkgJson.Type)
328+
}
317329
}
318330
} else {
319331
/**

server/config.go

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import (
99
"path"
1010
"path/filepath"
1111
"runtime"
12+
"slices"
1213
"strconv"
1314
"strings"
1415

1516
"github.com/esm-dev/esm.sh/internal/storage"
1617
"github.com/ije/gox/term"
18+
"github.com/ije/gox/utils"
1719
)
1820

1921
var (
@@ -68,12 +70,8 @@ type BanScope struct {
6870
}
6971

7072
type AllowList struct {
71-
Packages []string `json:"packages"`
72-
Scopes []AllowScope `json:"scopes"`
73-
}
74-
75-
type AllowScope struct {
76-
Name string `json:"name"`
73+
Packages []string `json:"packages"`
74+
Scopes []string `json:"scopes"`
7775
}
7876

7977
// LoadConfig loads config from the given file. Panic if failed to load.
@@ -248,77 +246,73 @@ func normalizeConfig(config *Config) {
248246

249247
// extractPackageName Will take a packageName as input extract key parts and return them
250248
//
251-
// fullNameWithoutVersion e.g. @github/faker
252-
// scope e.g. @github
253-
// nameWithoutVersionScope e.g. faker
254-
func extractPackageName(packageName string) (fullNameWithoutVersion string, scope string, nameWithoutVersionScope string) {
255-
paths := strings.Split(packageName, "/")
256-
if strings.HasPrefix(packageName, "@") {
249+
// moduleName e.g. @github/faker[@1.0.0]/submodule
250+
// packageId e.g. @github/faker[@1.0.0]
251+
// scope e.g. @github
252+
// name e.g. faker
253+
// version e.g. [@1.0.0]
254+
func extractPackageName(moduleName string) (packageId string, scope string, name string, version string) {
255+
paths := strings.Split(moduleName, "/")
256+
if strings.HasPrefix(moduleName, "@") && len(paths) > 1 {
257+
packageId = paths[0] + "/" + paths[1]
257258
scope = paths[0]
258-
nameWithoutVersionScope = strings.Split(paths[1], "@")[0]
259-
fullNameWithoutVersion = fmt.Sprintf("%s/%s", scope, nameWithoutVersionScope)
259+
name, version = utils.SplitByFirstByte(paths[1], '@')
260260
} else {
261261
// the package has no scope prefix
262-
nameWithoutVersionScope = strings.Split(paths[0], "@")[0]
263-
fullNameWithoutVersion = nameWithoutVersionScope
262+
packageId = paths[0]
263+
name, version = utils.SplitByFirstByte(packageId, '@')
264264
}
265-
266-
return fullNameWithoutVersion, scope, nameWithoutVersionScope
265+
return
267266
}
268267

269-
// IsPackageBanned Checking if the package is banned.
270-
// The `packages` list is the highest priority ban rule to match,
271-
// so the `excludes` list in the `scopes` list won't take effect if the package is banned in `packages` list
272-
func (banList *BanList) IsPackageBanned(fullName string) bool {
273-
fullNameWithoutVersion, scope, nameWithoutVersionScope := extractPackageName(fullName)
268+
func (allowList *AllowList) IsEmpty() bool {
269+
return len(allowList.Packages) == 0 && len(allowList.Scopes) == 0
270+
}
274271

275-
for _, p := range banList.Packages {
276-
if fullNameWithoutVersion == p {
277-
return true
278-
}
272+
// IsPackageAllowed Checking if the package is allowed.
273+
// The `packages` list is the highest priority allow rule to match,
274+
// so the `includes` list in the `scopes` list won't take effect if the package is allowed in `packages` list
275+
func (allowList *AllowList) IsPackageAllowed(moduleName string) bool {
276+
if allowList.IsEmpty() {
277+
return true
279278
}
280279

281-
for _, s := range banList.Scopes {
282-
if scope == s.Name {
283-
return !isPackageExcluded(nameWithoutVersionScope, s.Excludes)
284-
}
280+
packageId, scope, name, _ := extractPackageName(moduleName)
281+
282+
if slices.Contains(allowList.Packages, packageId) || (scope != "" && slices.Contains(allowList.Packages, scope+"/"+name)) || (scope == "" && slices.Contains(allowList.Packages, name)) {
283+
return true
285284
}
286285

287-
return false
286+
return slices.Contains(allowList.Scopes, scope)
288287
}
289288

290-
// IsPackageAllowed Checking if the package is allowed.
291-
// The `packages` list is the highest priority allow rule to match,
292-
// so the `includes` list in the `scopes` list won't take effect if the package is allowed in `packages` list
293-
func (allowList *AllowList) IsPackageAllowed(fullName string) bool {
294-
if len(allowList.Packages) == 0 && len(allowList.Scopes) == 0 {
295-
return true
289+
// IsPackageBanned Checking if the package is banned.
290+
// The `packages` list is the highest priority ban rule to match,
291+
// so the `excludes` list in the `scopes` list won't take effect if the package is banned in `packages` list
292+
func (banList *BanList) IsPackageBanned(moduleName string) bool {
293+
if banList.IsEmpty() {
294+
return false
296295
}
297296

298-
fullNameWithoutVersion, scope, _ := extractPackageName(fullName)
297+
packageId, scope, name, version := extractPackageName(moduleName)
299298

300-
for _, p := range allowList.Packages {
301-
if fullNameWithoutVersion == p {
302-
return true
303-
}
299+
if slices.Contains(banList.Packages, packageId) || (scope != "" && slices.Contains(banList.Packages, scope+"/"+name)) || (scope == "" && slices.Contains(banList.Packages, name)) {
300+
return true
304301
}
305302

306-
for _, s := range allowList.Scopes {
307-
if scope == s.Name {
308-
return true
303+
if scope != "" {
304+
for _, s := range banList.Scopes {
305+
if scope == s.Name {
306+
return !slices.Contains(s.Excludes, name) && !(version != "" && slices.Contains(s.Excludes, name+"@"+version))
307+
}
309308
}
310309
}
311310

312311
return false
313312
}
314313

315-
func isPackageExcluded(name string, excludes []string) bool {
316-
for _, exclude := range excludes {
317-
if name == exclude {
318-
return true
319-
}
320-
}
321-
return false
314+
func (banList *BanList) IsEmpty() bool {
315+
return len(banList.Packages) == 0 && len(banList.Scopes) == 0
322316
}
323317

324318
func init() {

server/config_test.go

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import (
66

77
func TestExtractPackageName(t *testing.T) {
88
type want struct {
9-
fullNameWithoutVersion string
10-
scope string
11-
nameWithoutVersionScope string
9+
packageId string
10+
scope string
11+
name string
12+
version string
1213
}
1314
tests := []struct {
1415
name string
@@ -18,37 +19,40 @@ func TestExtractPackageName(t *testing.T) {
1819
{
1920
name: "PackageWithVersionAndNoScope",
2021
packageName: "[email protected]",
21-
want: want{fullNameWithoutVersion: "faker", scope: "", nameWithoutVersionScope: "faker"},
22+
want: want{packageId: "faker@1.5.0", scope: "", name: "faker", version: "1.5.0"},
2223
},
2324
{
2425
name: "PackageWithVersionAndScope",
2526
packageName: "@github/[email protected]",
26-
want: want{fullNameWithoutVersion: "@github/faker", scope: "@github", nameWithoutVersionScope: "faker"},
27+
want: want{packageId: "@github/faker@1.5.0", scope: "@github", name: "faker", version: "1.5.0"},
2728
},
2829
{
2930
name: "ReactLoadedFromStable",
3031
packageName: "[email protected]/es2022/react.mjs",
31-
want: want{fullNameWithoutVersion: "react", scope: "", nameWithoutVersionScope: "react"},
32+
want: want{packageId: "react@18.2.0", scope: "", name: "react", version: "18.2.0"},
3233
},
3334
{
3435
name: "ScopedLoadedFromStable",
3536
packageName: "@github/[email protected]/es2022/faker.mjs",
36-
want: want{fullNameWithoutVersion: "@github/faker", scope: "@github", nameWithoutVersionScope: "faker"},
37+
want: want{packageId: "@github/faker@0.0.1", scope: "@github", name: "faker", version: "0.0.1"},
3738
},
3839
}
3940

4041
for _, tt := range tests {
4142
t.Run(tt.name, func(t *testing.T) {
42-
fullNameWithoutVersion, scope, nameWithoutVersionScope := extractPackageName(tt.packageName)
43+
fullNameWithoutVersion, scope, name, version := extractPackageName(tt.packageName)
4344

44-
if fullNameWithoutVersion != tt.want.fullNameWithoutVersion {
45-
t.Errorf("%s not equal %s", fullNameWithoutVersion, tt.want.fullNameWithoutVersion)
45+
if fullNameWithoutVersion != tt.want.packageId {
46+
t.Errorf("%s not equal %s", fullNameWithoutVersion, tt.want.packageId)
4647
}
4748
if scope != tt.want.scope {
4849
t.Errorf("%s not equal %s", scope, tt.want.scope)
4950
}
50-
if nameWithoutVersionScope != tt.want.nameWithoutVersionScope {
51-
t.Errorf("%s not equal %s", nameWithoutVersionScope, tt.want.nameWithoutVersionScope)
51+
if name != tt.want.name {
52+
t.Errorf("%s not equal %s", name, tt.want.name)
53+
}
54+
if version != tt.want.version {
55+
t.Errorf("%s not equal %s", version, tt.want.version)
5256
}
5357
})
5458
}
@@ -75,9 +79,7 @@ func TestAllowListAndBanList_IsPackageNotAllowedOrBanned(t *testing.T) {
7579
{
7680
name: "AllowedScopeBannedScope",
7781
allowList: AllowList{
78-
Scopes: []AllowScope{{
79-
Name: "@github",
80-
}},
82+
Scopes: []string{"@github"},
8183
},
8284
banList: BanList{
8385
Scopes: []BanScope{{
@@ -90,9 +92,7 @@ func TestAllowListAndBanList_IsPackageNotAllowedOrBanned(t *testing.T) {
9092
{
9193
name: "AllowedScopeBannedPackage",
9294
allowList: AllowList{
93-
Scopes: []AllowScope{{
94-
Name: "@github",
95-
}},
95+
Scopes: []string{"@github"},
9696
},
9797
banList: BanList{
9898
Packages: []string{"@github/faker"},
@@ -178,29 +178,23 @@ func TestAllowList_IsPackageAllowed(t *testing.T) {
178178
{
179179
name: "AllowedByScope",
180180
allowList: AllowList{
181-
Scopes: []AllowScope{{
182-
Name: "@github",
183-
}},
181+
Scopes: []string{"@github"},
184182
},
185183
args: args{fullName: "@github/perfect"},
186184
want: true,
187185
},
188186
{
189187
name: "NotAllowedByScope",
190188
allowList: AllowList{
191-
Scopes: []AllowScope{{
192-
Name: "@github",
193-
}},
189+
Scopes: []string{"@github"},
194190
},
195191
args: args{fullName: "@faker/perfect"},
196192
want: false,
197193
},
198194
{
199195
name: "NotAllowedByScope",
200196
allowList: AllowList{
201-
Scopes: []AllowScope{{
202-
Name: "@github",
203-
}},
197+
Scopes: []string{"@github"},
204198
},
205199
args: args{fullName: "@faker/perfect"},
206200
want: false,

server/router.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -854,9 +854,11 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex.
854854
return rex.Status(status, message)
855855
}
856856

857-
pkgAllowed := config.AllowList.IsPackageAllowed(esm.PkgName)
858-
pkgBanned := config.BanList.IsPackageBanned(esm.PkgName)
859-
if !pkgAllowed || pkgBanned {
857+
if !config.AllowList.IsEmpty() && !config.AllowList.IsPackageAllowed(esm.Name()) {
858+
return rex.Status(403, "forbidden")
859+
}
860+
861+
if !config.BanList.IsEmpty() && config.BanList.IsPackageBanned(esm.Name()) {
860862
return rex.Status(403, "forbidden")
861863
}
862864

0 commit comments

Comments
 (0)