11'use strict'
22
3+ const { encodeURIComponent } = require ( './encode' )
34const { isNullishOrEmptyString } = require ( './lang' )
4-
55const { createHelpersNamespaceObject } = require ( './helpers' )
6-
76const {
7+ isNonEmptyString,
88 isSemverString,
99 lowerName,
1010 lowerNamespace,
1111 lowerVersion,
1212 replaceDashesWithUnderscores,
1313 replaceUnderscoresWithDashes
1414} = require ( './strings' )
15-
1615const { validateEmptyByType, validateRequiredByType } = require ( './validate' )
1716const { PurlError } = require ( './error' )
1817
19- const PurlTypNormalizer = ( purl ) => purl
18+ const scopedPackagePattern = / ^ (?: @ ( [ ^ \/ ] + ? ) [ \/ ] ) ? ( [ ^ \/ ] + ? ) $ /
2019
20+ const PurlTypNormalizer = ( purl ) => purl
2121const PurlTypeValidator = ( _purl , _throws ) => true
2222
2323module . exports = {
@@ -104,7 +104,12 @@ module.exports = {
104104 // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#npm
105105 npm ( purl ) {
106106 lowerNamespace ( purl )
107- lowerName ( purl )
107+ // Ignore lowercasing names in cases where it might be a
108+ // legacy name because they could be mixed case.
109+ // https://github.com/npm/validate-npm-package-name/tree/v6.0.0?tab=readme-ov-file#legacy-names
110+ if ( isNonEmptyString ( purl . namespace ) ) {
111+ lowerName ( purl )
112+ }
108113 return purl
109114 } ,
110115 // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#luarocks
@@ -217,6 +222,119 @@ module.exports = {
217222 throws
218223 )
219224 } ,
225+ // Validation based on
226+ // https://www.npmjs.com/package/validate-npm-package-name
227+ // ISC License
228+ // Copyright (c) 2015, npm, Inc
229+ npm ( purl , throws ) {
230+ const { name, namespace : rawNamespace } = purl
231+ const namespace = isNonEmptyString ( rawNamespace )
232+ ? rawNamespace
233+ : ''
234+ const hasNamespace = namespace . length > 0
235+ const compName = hasNamespace ? 'namespace' : 'name'
236+ const id = `${ hasNamespace ? `${ namespace } /` : '' } ${ name } `
237+ const code0 = id . charCodeAt ( 0 )
238+ if ( code0 === 46 /*'.'*/ ) {
239+ if ( throws ) {
240+ throw new PurlError (
241+ `npm "${ compName } " component cannot start with a period`
242+ )
243+ }
244+ return false
245+ }
246+ if ( code0 === 95 /*'_'*/ ) {
247+ if ( throws ) {
248+ throw new PurlError (
249+ `npm "${ compName } " component cannot start with an underscore`
250+ )
251+ }
252+ return false
253+ }
254+ const loweredId = id . toLowerCase ( )
255+ if (
256+ loweredId === 'node_modules' ||
257+ loweredId === 'favicon.ico'
258+ ) {
259+ if ( throws ) {
260+ throw new PurlError (
261+ `npm "${ compName } " component of "${ loweredId } " is not allowed`
262+ )
263+ }
264+ return false
265+ }
266+ if ( hasNamespace ) {
267+ if ( code0 !== 64 /*'@'*/ ) {
268+ throw new PurlError (
269+ `npm "namespace" component must start with an "@" character`
270+ )
271+ }
272+ if ( namespace . trim ( ) !== namespace ) {
273+ if ( throws ) {
274+ throw new PurlError (
275+ 'npm "namespace" component cannot contain leading or trailing spaces'
276+ )
277+ }
278+ return false
279+ }
280+ const namespaceWithoutAtSign = namespace . slice ( 1 )
281+ if (
282+ encodeURIComponent ( namespaceWithoutAtSign ) !==
283+ namespaceWithoutAtSign
284+ ) {
285+ if ( throws ) {
286+ throw new PurlError (
287+ `npm "namespace" component can only contain URL-friendly characters`
288+ )
289+ }
290+ return false
291+ }
292+ // The remaining checks in this block are modern name
293+ // restrictions. We apply these checks when a namespace
294+ // is present because legacy names did not have namespaces.
295+ if ( id . length > 214 ) {
296+ if ( throws ) {
297+ throw new PurlError (
298+ `npm "namespace" and "name" components can not collectively be more than 214 characters`
299+ )
300+ }
301+ return false
302+ }
303+ if ( loweredId !== id ) {
304+ if ( throws ) {
305+ throw new PurlError (
306+ `npm "name" component can not contain capital letters`
307+ )
308+ }
309+ return false
310+ }
311+ if ( / [ ~ ' ! ( ) * ] / . test ( name ) ) {
312+ if ( throws ) {
313+ throw new PurlError (
314+ `npm "name" component can not contain special characters ("~\'!()*")`
315+ )
316+ }
317+ return false
318+ }
319+ }
320+ if ( name . trim ( ) !== name ) {
321+ if ( throws ) {
322+ throw new PurlError (
323+ 'npm "name" component cannot contain leading or trailing spaces'
324+ )
325+ }
326+ return false
327+ }
328+ if ( encodeURIComponent ( name ) !== name ) {
329+ if ( throws ) {
330+ throw new PurlError (
331+ `npm "name" component can only contain URL-friendly characters`
332+ )
333+ }
334+ return false
335+ }
336+ return true
337+ } ,
220338 // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#oci
221339 oci ( purl , throws ) {
222340 return validateEmptyByType (
@@ -233,21 +351,21 @@ module.exports = {
233351 const code = name . charCodeAt ( i )
234352 // prettier-ignore
235353 if (
236- ! (
237- (
238- ( code >= 48 && code <= 57 ) || // 0-9
239- ( code >= 97 && code <= 122 ) || // a-z
240- code === 95 // _
241- )
242- )
243- ) {
244- if ( throws ) {
245- throw new PurlError (
246- 'pub "name" component may only contain [a-z0-9_] characters'
247- )
248- }
249- return false
250- }
354+ ! (
355+ (
356+ ( code >= 48 && code <= 57 ) || // 0-9
357+ ( code >= 97 && code <= 122 ) || // a-z
358+ code === 95 // _
359+ )
360+ )
361+ ) {
362+ if ( throws ) {
363+ throw new PurlError (
364+ 'pub "name" component may only contain [a-z0-9_] characters'
365+ )
366+ }
367+ return false
368+ }
251369 }
252370 return true
253371 } ,
0 commit comments