View Javadoc
1   /*
2    * This file is part of dependency-check-core.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Copyright (c) 2015 The OWASP Foundation. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.composer;
19  
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import jakarta.json.Json;
24  import jakarta.json.JsonArray;
25  import jakarta.json.JsonException;
26  import jakarta.json.JsonObject;
27  import jakarta.json.JsonReader;
28  import jakarta.json.stream.JsonParsingException;
29  import java.io.InputStream;
30  import java.util.ArrayList;
31  import java.util.List;
32  import javax.annotation.concurrent.NotThreadSafe;
33  
34  /**
35   * Parses a Composer.lock file from an input stream. In a separate class so it
36   * can hopefully be injected.
37   *
38   * @author colezlaw
39   */
40  @NotThreadSafe
41  public class ComposerLockParser {
42  
43      /**
44       * The JsonReader for parsing JSON
45       */
46      private final JsonReader jsonReader;
47      /**
48       * The List of ComposerDependencies found
49       */
50      private final List<ComposerDependency> composerDependencies;
51      /**
52       * Whether to skip dev dependencies.
53       */
54      private final boolean skipDev;
55      /**
56       * The LOGGER
57       */
58      private static final Logger LOGGER = LoggerFactory.getLogger(ComposerLockParser.class);
59  
60      /**
61       * Creates a ComposerLockParser from a JsonReader and an InputStream.
62       *
63       * @param inputStream the InputStream to parse
64       * @param skipDev whether to skip dev dependencies
65       */
66      public ComposerLockParser(InputStream inputStream, boolean skipDev) {
67          LOGGER.debug("Creating a ComposerLockParser");
68          this.jsonReader = Json.createReader(inputStream);
69          this.composerDependencies = new ArrayList<>();
70          this.skipDev = skipDev;
71      }
72  
73      /**
74       * Process the input stream to create the list of dependencies.
75       */
76      public void process() {
77          LOGGER.debug("Beginning Composer lock processing");
78          try {
79              final JsonObject composer = jsonReader.readObject();
80              if (composer.containsKey("packages")) {
81                  LOGGER.debug("Found packages");
82                  final JsonArray packages = composer.getJsonArray("packages");
83                  for (JsonObject pkg : packages.getValuesAs(JsonObject.class)) {
84                      processPackageEntry(pkg);
85                  }
86              }
87              if (composer.containsKey("packages-dev") && !skipDev) {
88                  LOGGER.debug("Found packages-dev");
89                  final JsonArray devPackages = composer.getJsonArray("packages-dev");
90                  for (JsonObject pkg : devPackages.getValuesAs(JsonObject.class)) {
91                      processPackageEntry(pkg);
92                  }
93              }
94          } catch (JsonParsingException jsonpe) {
95              throw new ComposerException("Error parsing stream", jsonpe);
96          } catch (JsonException jsone) {
97              throw new ComposerException("Error reading stream", jsone);
98          } catch (IllegalStateException ise) {
99              throw new ComposerException("Illegal state in composer stream", ise);
100         } catch (ClassCastException cce) {
101             throw new ComposerException("Not exactly composer lock", cce);
102         }
103     }
104 
105     protected void processPackageEntry(JsonObject pkg) {
106         if (pkg.containsKey("name")) {
107             final String groupName = pkg.getString("name");
108             if (groupName.indexOf('/') >= 0 && groupName.indexOf('/') <= groupName.length() - 1) {
109                 if (pkg.containsKey("version")) {
110                     final String group = groupName.substring(0, groupName.indexOf('/'));
111                     final String project = groupName.substring(groupName.indexOf('/') + 1);
112                     String version = pkg.getString("version");
113                     // Some version numbers begin with v - which doesn't end up matching CPE's
114                     if (version.startsWith("v")) {
115                         version = version.substring(1);
116                     }
117                     LOGGER.debug("Got package {}/{}/{}", group, project, version);
118                     composerDependencies.add(new ComposerDependency(group, project, version));
119                 } else {
120                     LOGGER.debug("Group/package {} does not have a version", groupName);
121                 }
122             } else {
123                 LOGGER.debug("Got a dependency with no name");
124             }
125         }
126     }
127 
128     /**
129      * Gets the list of dependencies.
130      *
131      * @return the list of dependencies
132      */
133     public List<ComposerDependency> getDependencies() {
134         return composerDependencies;
135     }
136 }