Files
codeql/javascript/ql/lib/semmle/javascript/NPM.qll
2021-11-01 09:51:15 +01:00

336 lines
11 KiB
Plaintext

/**
* Provides classes for working with NPM module definitions and dependencies.
*/
import javascript
private import NodeModuleResolutionImpl
/** A `package.json` configuration object. */
class PackageJSON extends JSONObject {
PackageJSON() {
this.getJsonFile().getBaseName() = "package.json" and
this.isTopLevel()
}
/** Gets the name of this package. */
string getPackageName() { result = this.getPropStringValue("name") }
/** Gets the version of this package. */
string getVersion() { result = this.getPropStringValue("version") }
/** Gets the description of this package. */
string getDescription() { result = this.getPropStringValue("description") }
/** Gets the array of keywords for this package. */
JSONArray getKeywords() { result = this.getPropValue("keywords") }
/** Gets a keyword for this package. */
string getAKeyword() { result = this.getKeywords().getElementStringValue(_) }
/** Gets the homepage URL of this package. */
string getHomepage() { result = this.getPropStringValue("homepage") }
/** Gets the bug tracker information of this package. */
BugTrackerInfo getBugs() { result = this.getPropValue("bugs") }
/** Gets the license information of this package. */
string getLicense() { result = this.getPropStringValue("license") }
/** Gets the author information of this package. */
ContributorInfo getAuthor() { result = this.getPropValue("author") }
/** Gets information for a contributor to this package. */
ContributorInfo getAContributor() {
result = this.getPropValue("contributors").getElementValue(_)
}
/** Gets the array of files for this package. */
JSONArray getFiles() { result = this.getPropValue("files") }
/** Gets a file for this package. */
string getAFile() { result = this.getFiles().getElementStringValue(_) }
/** Gets the main module of this package. */
string getMain() { result = MainModulePath::of(this).getValue() }
/** Gets the path of a command defined for this package. */
string getBin(string cmd) {
cmd = this.getPackageName() and result = this.getPropStringValue("bin")
or
result = this.getPropValue("bin").getPropValue(cmd).getStringValue()
}
/** Gets a manual page for this package. */
string getAManFile() {
result = this.getPropStringValue("man") or
result = this.getPropValue("man").getElementValue(_).getStringValue()
}
/** Gets information about the directories of this package. */
JSONObject getDirectories() { result = this.getPropValue("directories") }
/** Gets repository information for this package. */
RepositoryInfo getRepository() { result = this.getPropValue("repository") }
/** Gets information about the scripts of this package. */
JSONObject getScripts() { result = this.getPropValue("scripts") }
/** Gets configuration information for this package. */
JSONObject getConfig() { result = this.getPropValue("config") }
/** Gets the dependencies of this package. */
PackageDependencies getDependencies() { result = this.getPropValue("dependencies") }
/** Gets the development dependencies of this package. */
PackageDependencies getDevDependencies() { result = this.getPropValue("devDependencies") }
/** Gets the peer dependencies of this package. */
PackageDependencies getPeerDependencies() { result = this.getPropValue("peerDependencies") }
/** Gets the bundled dependencies of this package. */
PackageDependencies getBundledDependencies() {
result = this.getPropValue("bundledDependencies") or
result = this.getPropValue("bundleDependencies")
}
/** Gets the optional dependencies of this package. */
PackageDependencies getOptionalDependencies() {
result = this.getPropValue("optionalDependencies")
}
/**
* Gets a JSON object describing a group of dependencies of
* this package of the kind specified by `depkind`:
* `""` for normal dependencies, `"dev"` for `devDependencies`,
* `"bundled"` for `bundledDependencies` and `"opt"` for
* `optionalDependencies`.
*/
PackageDependencies getADependenciesObject(string depkind) {
result = this.getDependencies() and depkind = ""
or
result = this.getDevDependencies() and depkind = "dev"
or
result = this.getBundledDependencies() and depkind = "bundled"
or
result = this.getOptionalDependencies() and depkind = "opt"
}
/**
* Holds if this package declares a dependency (including
* optional, development and bundled dependencies) on the given version
* of the given package.
*
* This does _not_ consider peer dependencies, which are semantically
* different from the other dependency types.
*/
predicate declaresDependency(string pkg, string version) {
this.getADependenciesObject(_).getADependency(pkg, version)
}
/** Gets the engine dependencies of this package. */
PackageDependencies getEngines() { result = this.getPropValue("engines") }
/** Holds if this package has strict engine requirements. */
predicate isEngineStrict() { this.getPropValue("engineStrict").(JSONBoolean).getValue() = "true" }
/** Gets information about operating systems supported by this package. */
JSONArray getOSs() { result = this.getPropValue("os") }
/** Gets an operating system supported by this package. */
string getWhitelistedOS() {
result = this.getOSs().getElementStringValue(_) and
not result.matches("!%")
}
/** Gets an operating system not supported by this package. */
string getBlacklistedOS() {
exists(string str | str = this.getOSs().getElementStringValue(_) |
result = str.regexpCapture("!(.*)", 1)
)
}
/** Gets information about platforms supported by this package. */
JSONArray getCPUs() { result = this.getPropValue("cpu") }
/** Gets a platform supported by this package. */
string getWhitelistedCPU() {
result = this.getCPUs().getElementStringValue(_) and
not result.matches("!%")
}
/** Gets a platform not supported by this package. */
string getBlacklistedCPU() {
exists(string str | str = this.getCPUs().getElementStringValue(_) |
result = str.regexpCapture("!(.*)", 1)
)
}
/** Holds if this package prefers to be installed globally. */
predicate isPreferGlobal() { this.getPropValue("preferGlobal").(JSONBoolean).getValue() = "true" }
/** Holds if this is a private package. */
predicate isPrivate() { this.getPropValue("private").(JSONBoolean).getValue() = "true" }
/** Gets publishing configuration information about this package. */
JSONValue getPublishConfig() { result = this.getPropValue("publishConfig") }
/**
* Gets the main module of this package.
*/
Module getMainModule() {
result = min(Module m, int prio | m.getFile() = resolveMainModule(this, prio) | m order by prio)
}
}
/**
* A representation of bug tracker information for an NPM package.
*/
class BugTrackerInfo extends JSONValue {
BugTrackerInfo() {
exists(PackageJSON pkg | pkg.getPropValue("bugs") = this) and
(this instanceof JSONObject or this instanceof JSONString)
}
/** Gets the bug tracker URL. */
string getUrl() {
result = this.getPropValue("url").getStringValue() or
result = this.getStringValue()
}
/** Gets the bug reporting email address. */
string getEmail() { result = this.getPropValue("email").getStringValue() }
}
/**
* A representation of contributor information for an NPM package.
*/
class ContributorInfo extends JSONValue {
ContributorInfo() {
exists(PackageJSON pkg |
this = pkg.getPropValue("author") or
this = pkg.getPropValue("contributors").getElementValue(_)
) and
(this instanceof JSONObject or this instanceof JSONString)
}
/**
* Gets the `i`th item of information about a contributor, where the first
* item is their name, the second their email address, and the third their
* homepage URL.
*/
private string parseInfo(int group) {
result = this.getStringValue().regexpCapture("(.*?)(?: <(.*?)>)?(?: \\((.*)?\\))", group)
}
/** Gets the contributor's name. */
string getName() {
result = this.getPropValue("name").getStringValue() or
result = this.parseInfo(1)
}
/** Gets the contributor's email address. */
string getEmail() {
result = this.getPropValue("email").getStringValue() or
result = this.parseInfo(2)
}
/** Gets the contributor's homepage URL. */
string getUrl() {
result = this.getPropValue("url").getStringValue() or
result = this.parseInfo(3)
}
}
/**
* A representation of repository information for an NPM package.
*/
class RepositoryInfo extends JSONObject {
RepositoryInfo() { exists(PackageJSON pkg | this = pkg.getPropValue("repository")) }
/** Gets the repository type. */
string getType() { result = this.getPropStringValue("type") }
/** Gets the repository URL. */
string getUrl() { result = this.getPropStringValue("url") }
}
/**
* A representation of package dependencies for an NPM package.
*/
class PackageDependencies extends JSONObject {
PackageDependencies() {
exists(PackageJSON pkg, string name |
name.regexpMatch("(.+D|d)ependencies|engines") and
this = pkg.getPropValue(name)
)
}
/** Holds if this package depends on version 'version' of package 'pkg'. */
predicate getADependency(string pkg, string version) { version = this.getPropStringValue(pkg) }
}
/**
* An NPM package.
*/
class NPMPackage extends @folder {
/** The `package.json` file of this package. */
PackageJSON pkg;
NPMPackage() { pkg.getJsonFile().getParentContainer() = this }
/** Gets a textual representation of this package. */
string toString() { result = this.(Folder).toString() }
/** Gets the full file system path of this package. */
string getPath() { result = this.(Folder).getAbsolutePath() }
/** Gets the `package.json` object of this package. */
PackageJSON getPackageJSON() { result = pkg }
/** Gets the name of this package. */
string getPackageName() { result = this.getPackageJSON().getPackageName() }
/** Gets the `node_modules` folder of this package. */
Folder getNodeModulesFolder() {
result.getBaseName() = "node_modules" and
result.getParentContainer() = this
}
/**
* Gets a file belonging to this package.
*
* We only consider files to belong to the nearest enclosing package,
* and files inside the `node_modules` folder of a package are not
* considered to belong to that package.
*/
File getAFile() { this = packageInternalParent*(result.getParentContainer()) }
/**
* Gets a Node.js module belonging to this package.
*
* We only consider modules to belong to the nearest enclosing package,
* and modules inside the `node_modules` folder of a package are not
* considered to belong to that package.
*/
Module getAModule() { result.getFile() = this.getAFile() }
/**
* Gets the main module of this package.
*/
Module getMainModule() { result = pkg.getMainModule() }
/**
* Holds if this package declares a dependency on version `v` of package `p`.
*/
predicate declaresDependency(string p, string v) { pkg.declaresDependency(p, v) }
}
/**
* Gets the parent folder of `c`, provided that they belong to the same NPM
* package; that is, `c` must not be a `node_modules` folder.
*/
private Folder packageInternalParent(Container c) {
result = c.getParentContainer() and
not c.(Folder).getBaseName() = "node_modules"
}