mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
feat(php): add support for dev dependencies in Composer (#9910)
This commit is contained in:
@@ -12,8 +12,8 @@ The following table provides an outline of the features Trivy offers.
|
|||||||
|
|
||||||
|
|
||||||
| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position |
|
| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position |
|
||||||
|-----------------|----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|
|
|-----------------|----------------|:-----------------------:|:----------------------------------:|:------------------------------------:|:--------:|
|
||||||
| Composer | composer.lock | ✓ | Excluded | ✓ | ✓ |
|
| Composer | composer.lock | ✓ | [Excluded](#development-dependencies) | ✓ | ✓ |
|
||||||
| Composer | installed.json | ✓ | Excluded | - | ✓ |
|
| Composer | installed.json | ✓ | Excluded | - | ✓ |
|
||||||
|
|
||||||
## composer.lock
|
## composer.lock
|
||||||
@@ -23,6 +23,12 @@ Trivy also supports dependency trees; however, to display an accurate tree, it n
|
|||||||
Since this information is not included in `composer.lock`, Trivy parses `composer.json`, which should be located next to `composer.lock`.
|
Since this information is not included in `composer.lock`, Trivy parses `composer.json`, which should be located next to `composer.lock`.
|
||||||
If you want to see the dependency tree, please ensure that `composer.json` is present.
|
If you want to see the dependency tree, please ensure that `composer.json` is present.
|
||||||
|
|
||||||
|
### Development dependencies
|
||||||
|
By default, Trivy doesn't report development dependencies (`packages-dev` in `composer.lock`).
|
||||||
|
Use the `--include-dev-deps` flag to include them.
|
||||||
|
|
||||||
|
To correctly identify direct development dependencies, Trivy parses `require-dev` from `composer.json`, which should be located next to `composer.lock`.
|
||||||
|
|
||||||
## installed.json
|
## installed.json
|
||||||
Trivy also supports dependency detection for `installed.json` files. By default, you can find this file at `path_to_app/vendor/composer/installed.json`.
|
Trivy also supports dependency detection for `installed.json` files. By default, you can find this file at `path_to_app/vendor/composer/installed.json`.
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
type LockFile struct {
|
type LockFile struct {
|
||||||
Packages []packageInfo `json:"packages"`
|
Packages []packageInfo `json:"packages"`
|
||||||
|
PackagesDev []packageInfo `json:"packages-dev"`
|
||||||
}
|
}
|
||||||
type packageInfo struct {
|
type packageInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -45,30 +46,11 @@ func (p *Parser) Parse(_ context.Context, r xio.ReadSeekerAt) ([]ftypes.Package,
|
|||||||
|
|
||||||
pkgs := make(map[string]ftypes.Package)
|
pkgs := make(map[string]ftypes.Package)
|
||||||
foundDeps := make(map[string][]string)
|
foundDeps := make(map[string][]string)
|
||||||
for _, lpkg := range lockFile.Packages {
|
|
||||||
pkg := ftypes.Package{
|
|
||||||
ID: dependency.ID(ftypes.Composer, lpkg.Name, lpkg.Version),
|
|
||||||
Name: lpkg.Name,
|
|
||||||
Version: lpkg.Version,
|
|
||||||
Relationship: ftypes.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies
|
|
||||||
Licenses: licenses(lpkg.License),
|
|
||||||
Locations: []ftypes.Location{ftypes.Location(lpkg.Location)},
|
|
||||||
}
|
|
||||||
pkgs[pkg.Name] = pkg
|
|
||||||
|
|
||||||
var dependsOn []string
|
// Production packages are parsed first to ensure they take precedence
|
||||||
for depName := range lpkg.Require {
|
// when the same package exists in both "packages" and "packages-dev".
|
||||||
// Require field includes required php version, skip this
|
p.parseProdPackages(lockFile, pkgs, foundDeps)
|
||||||
// Also skip PHP extensions
|
p.parseDevPackages(lockFile, pkgs, foundDeps)
|
||||||
if depName == "php" || strings.HasPrefix(depName, "ext") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the packages
|
|
||||||
}
|
|
||||||
if len(dependsOn) > 0 {
|
|
||||||
foundDeps[pkg.ID] = dependsOn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill deps versions
|
// fill deps versions
|
||||||
var deps ftypes.Dependencies
|
var deps ftypes.Dependencies
|
||||||
@@ -95,6 +77,50 @@ func (p *Parser) Parse(_ context.Context, r xio.ReadSeekerAt) ([]ftypes.Package,
|
|||||||
return pkgSlice, deps, nil
|
return pkgSlice, deps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseProdPackages parses packages from the "packages" field in composer.lock.
|
||||||
|
func (p *Parser) parseProdPackages(lockFile LockFile, pkgs map[string]ftypes.Package, foundDeps map[string][]string) {
|
||||||
|
p.parsePackages(lockFile.Packages, false, pkgs, foundDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDevPackages parses packages from the "packages-dev" field in composer.lock.
|
||||||
|
// Packages already present in pkgs (i.e., production packages) are skipped.
|
||||||
|
func (p *Parser) parseDevPackages(lockFile LockFile, pkgs map[string]ftypes.Package, foundDeps map[string][]string) {
|
||||||
|
p.parsePackages(lockFile.PackagesDev, true, pkgs, foundDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parsePackages(lockPkgs []packageInfo, isDev bool, pkgs map[string]ftypes.Package, foundDeps map[string][]string) {
|
||||||
|
for _, lpkg := range lockPkgs {
|
||||||
|
// Skip if the package already exists (production packages take precedence over dev packages)
|
||||||
|
if _, ok := pkgs[lpkg.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg := ftypes.Package{
|
||||||
|
ID: dependency.ID(ftypes.Composer, lpkg.Name, lpkg.Version),
|
||||||
|
Name: lpkg.Name,
|
||||||
|
Version: lpkg.Version,
|
||||||
|
Relationship: ftypes.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies
|
||||||
|
Licenses: licenses(lpkg.License),
|
||||||
|
Locations: []ftypes.Location{ftypes.Location(lpkg.Location)},
|
||||||
|
Dev: isDev,
|
||||||
|
}
|
||||||
|
pkgs[pkg.Name] = pkg
|
||||||
|
|
||||||
|
var dependsOn []string
|
||||||
|
for depName := range lpkg.Require {
|
||||||
|
// Require field includes required php version, skip this
|
||||||
|
// Also skip PHP extensions
|
||||||
|
if depName == "php" || strings.HasPrefix(depName, "ext") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the packages
|
||||||
|
}
|
||||||
|
if len(dependsOn) > 0 {
|
||||||
|
foundDeps[pkg.ID] = dependsOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// licenses returns slice of licenses from string, string with separators (`or`, `and`, etc.) or string array
|
// licenses returns slice of licenses from string, string with separators (`or`, `and`, etc.) or string array
|
||||||
// cf. https://getcomposer.org/doc/04-schema.md#license
|
// cf. https://getcomposer.org/doc/04-schema.md#license
|
||||||
func licenses(val any) []string {
|
func licenses(val any) []string {
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "pear/log@1.13.3",
|
||||||
|
Name: "pear/log",
|
||||||
|
Version: "1.13.3",
|
||||||
|
Dev: true,
|
||||||
|
Licenses: []string{"MIT"},
|
||||||
|
Locations: []ftypes.Location{
|
||||||
|
{
|
||||||
|
StartLine: 660,
|
||||||
|
EndLine: 719,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "pear/pear_exception@v1.0.2",
|
||||||
|
Name: "pear/pear_exception",
|
||||||
|
Version: "v1.0.2",
|
||||||
|
Dev: true,
|
||||||
|
Licenses: []string{"BSD-2-Clause"},
|
||||||
|
Locations: []ftypes.Location{
|
||||||
|
{
|
||||||
|
StartLine: 720,
|
||||||
|
EndLine: 778,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "psr/http-message@1.0.1",
|
ID: "psr/http-message@1.0.1",
|
||||||
Name: "psr/http-message",
|
Name: "psr/http-message",
|
||||||
@@ -132,6 +158,12 @@ var (
|
|||||||
"ralouphie/getallheaders@3.0.3",
|
"ralouphie/getallheaders@3.0.3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "pear/log@1.13.3",
|
||||||
|
DependsOn: []string{
|
||||||
|
"pear/pear_exception@v1.0.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "symfony/polyfill-intl-idn@v1.27.0",
|
ID: "symfony/polyfill-intl-idn@v1.27.0",
|
||||||
DependsOn: []string{
|
DependsOn: []string{
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (a composerAnalyzer) parseComposerLock(ctx context.Context, path string, r
|
|||||||
func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.Application) error {
|
func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.Application) error {
|
||||||
// Parse composer.json to identify the direct dependencies
|
// Parse composer.json to identify the direct dependencies
|
||||||
path := filepath.Join(dir, types.ComposerJson)
|
path := filepath.Join(dir, types.ComposerJson)
|
||||||
p, err := a.parseComposerJson(fsys, path)
|
cj, err := a.parseComposerJson(fsys, path)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
// Assume all the packages are direct dependencies as it cannot identify them from composer.lock
|
// Assume all the packages are direct dependencies as it cannot identify them from composer.lock
|
||||||
log.Debug("Unable to determine the direct dependencies, composer.json not found", log.FilePath(path))
|
log.Debug("Unable to determine the direct dependencies, composer.json not found", log.FilePath(path))
|
||||||
@@ -117,7 +117,9 @@ func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.A
|
|||||||
|
|
||||||
for i, pkg := range app.Packages {
|
for i, pkg := range app.Packages {
|
||||||
// Identify the direct/transitive dependencies
|
// Identify the direct/transitive dependencies
|
||||||
if _, ok := p[pkg.Name]; ok {
|
if _, ok := cj.Require[pkg.Name]; ok {
|
||||||
|
app.Packages[i].Relationship = types.RelationshipDirect
|
||||||
|
} else if _, ok := cj.RequireDev[pkg.Name]; ok {
|
||||||
app.Packages[i].Relationship = types.RelationshipDirect
|
app.Packages[i].Relationship = types.RelationshipDirect
|
||||||
} else {
|
} else {
|
||||||
app.Packages[i].Indirect = true
|
app.Packages[i].Indirect = true
|
||||||
@@ -130,20 +132,21 @@ func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.A
|
|||||||
|
|
||||||
type composerJson struct {
|
type composerJson struct {
|
||||||
Require map[string]string `json:"require"`
|
Require map[string]string `json:"require"`
|
||||||
|
RequireDev map[string]string `json:"require-dev"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a composerAnalyzer) parseComposerJson(fsys fs.FS, path string) (map[string]string, error) {
|
func (a composerAnalyzer) parseComposerJson(fsys fs.FS, path string) (composerJson, error) {
|
||||||
// Parse composer.json
|
// Parse composer.json
|
||||||
f, err := fsys.Open(path)
|
f, err := fsys.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("file open error: %w", err)
|
return composerJson{}, xerrors.Errorf("file open error: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = f.Close() }()
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
jsonFile := composerJson{}
|
var jsonFile composerJson
|
||||||
err = json.NewDecoder(f).Decode(&jsonFile)
|
err = json.NewDecoder(f).Decode(&jsonFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("json decode error: %w", err)
|
return composerJson{}, xerrors.Errorf("json decode error: %w", err)
|
||||||
}
|
}
|
||||||
return jsonFile.Require, nil
|
return jsonFile, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,6 +151,65 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) {
|
|||||||
dir: "testdata/composer/sad",
|
dir: "testdata/composer/sad",
|
||||||
want: &analyzer.AnalysisResult{},
|
want: &analyzer.AnalysisResult{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "with dev dependencies",
|
||||||
|
dir: "testdata/composer/with-dev",
|
||||||
|
want: &analyzer.AnalysisResult{
|
||||||
|
Applications: []types.Application{
|
||||||
|
{
|
||||||
|
Type: types.Composer,
|
||||||
|
FilePath: "composer.lock",
|
||||||
|
Packages: types.Packages{
|
||||||
|
{
|
||||||
|
ID: "pear/log@1.14.6",
|
||||||
|
Name: "pear/log",
|
||||||
|
Version: "1.14.6",
|
||||||
|
Dev: true,
|
||||||
|
Indirect: false,
|
||||||
|
Relationship: types.RelationshipDirect,
|
||||||
|
Licenses: []string{"MIT"},
|
||||||
|
Locations: []types.Location{
|
||||||
|
{
|
||||||
|
StartLine: 61,
|
||||||
|
EndLine: 121,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DependsOn: []string{"pear/pear_exception@v1.0.2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "psr/log@1.1.4",
|
||||||
|
Name: "psr/log",
|
||||||
|
Version: "1.1.4",
|
||||||
|
Indirect: false,
|
||||||
|
Relationship: types.RelationshipDirect,
|
||||||
|
Licenses: []string{"MIT"},
|
||||||
|
Locations: []types.Location{
|
||||||
|
{
|
||||||
|
StartLine: 9,
|
||||||
|
EndLine: 58,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "pear/pear_exception@v1.0.2",
|
||||||
|
Name: "pear/pear_exception",
|
||||||
|
Version: "v1.0.2",
|
||||||
|
Dev: true,
|
||||||
|
Indirect: true,
|
||||||
|
Relationship: types.RelationshipIndirect,
|
||||||
|
Licenses: []string{"BSD-2-Clause"},
|
||||||
|
Locations: []types.Location{
|
||||||
|
{
|
||||||
|
StartLine: 122,
|
||||||
|
EndLine: 180,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
8
pkg/fanal/analyzer/language/php/composer/testdata/composer/with-dev/composer.json
vendored
Normal file
8
pkg/fanal/analyzer/language/php/composer/testdata/composer/with-dev/composer.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"psr/log": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"pear/log": "^1.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
190
pkg/fanal/analyzer/language/php/composer/testdata/composer/with-dev/composer.lock
generated
vendored
Normal file
190
pkg/fanal/analyzer/language/php/composer/testdata/composer/with-dev/composer.lock
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "2c9e13a2460669ca09226814c0aefb51",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "psr/log",
|
||||||
|
"version": "1.1.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/log.git",
|
||||||
|
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||||
|
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Log\\": "Psr/Log/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for logging libraries",
|
||||||
|
"homepage": "https://github.com/php-fig/log",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"psr",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||||
|
},
|
||||||
|
"time": "2021-05-03T11:20:27+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "pear/log",
|
||||||
|
"version": "1.14.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/pear/Log.git",
|
||||||
|
"reference": "e136d31ff6d5991e9707862f5fbfb97d40cd37a3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/pear/Log/zipball/e136d31ff6d5991e9707862f5fbfb97d40cd37a3",
|
||||||
|
"reference": "e136d31ff6d5991e9707862f5fbfb97d40cd37a3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"pear/pear_exception": "1.0.1 || 1.0.2",
|
||||||
|
"php": ">=7.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "*",
|
||||||
|
"rector/rector": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"pear/db": "Install optionally via your project's composer.json"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Log": "./"
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/examples/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"include-path": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jon Parise",
|
||||||
|
"email": "jon@php.net",
|
||||||
|
"homepage": "https://www.indelible.org/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PEAR Logging Framework",
|
||||||
|
"homepage": "https://pear.github.io/Log/",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"logging"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/pear/Log/issues",
|
||||||
|
"source": "https://github.com/pear/Log"
|
||||||
|
},
|
||||||
|
"time": "2025-07-27T00:25:20+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pear/pear_exception",
|
||||||
|
"version": "v1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/pear/PEAR_Exception.git",
|
||||||
|
"reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
|
||||||
|
"reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "<9"
|
||||||
|
},
|
||||||
|
"type": "class",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"PEAR/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"include-path": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"license": [
|
||||||
|
"BSD-2-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Helgi Thormar",
|
||||||
|
"email": "dufuz@php.net"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Greg Beaver",
|
||||||
|
"email": "cellog@php.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The PEAR Exception base class.",
|
||||||
|
"homepage": "https://github.com/pear/PEAR_Exception",
|
||||||
|
"keywords": [
|
||||||
|
"exception"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception",
|
||||||
|
"source": "https://github.com/pear/PEAR_Exception"
|
||||||
|
},
|
||||||
|
"time": "2021-03-21T15:43:46+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {},
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {},
|
||||||
|
"platform-dev": {},
|
||||||
|
"plugin-api-version": "2.9.0"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user