View Javadoc

    1   /*
    2    * This file is part of dependency-check-core.
    3    *
    4    * Dependency-check-core is free software: you can redistribute it and/or modify it
    5    * under the terms of the GNU General Public License as published by the Free
    6    * Software Foundation, either version 3 of the License, or (at your option) any
    7    * later version.
    8    *
    9    * Dependency-check-core is distributed in the hope that it will be useful, but
    10   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
    12   * details.
    13   *
    14   * You should have received a copy of the GNU General Public License along with
    15   * dependency-check-core. If not, see http://www.gnu.org/licenses/.
    16   *
    17   * Copyright (c) 2012 Jeremy Long. All Rights Reserved.
    18   */
    19  package org.owasp.dependencycheck.dependency;
    20
    21  import java.io.Serializable;
    22  import java.io.UnsupportedEncodingException;
    23  import java.net.URLDecoder;
    24  import java.util.logging.Level;
    25  import java.util.logging.Logger;
    26  import org.owasp.dependencycheck.data.cpe.IndexEntry;
    27
    28  /**
    29   * A record containing information about vulnerable software. This is referenced
    30   * from a vulnerability.
    31   *
    32   * @author Jeremy Long (jeremy.long@owasp.org)
    33   */
    34  public class VulnerableSoftware extends IndexEntry implements Serializable, Comparable<VulnerableSoftware> {
    35
    36      /**
    37       * The serial version UID.
    38       */
    39      private static final long serialVersionUID = 307319490326651052L;
    40
    41      /**
    42       * Parse a CPE entry from the cpe string representation.
    43       *
    44       * @param cpe a cpe entry (e.g. cpe:/a:vendor:software:version)
    45       */
    46      public void setCpe(String cpe) {
    47          try {
    48              parseName(cpe);
    49          } catch (UnsupportedEncodingException ex) {
    50              final String msg = String.format("Character encoding is unsupported for CPE '%s'.", cpe);
    51              Logger.getLogger(VulnerableSoftware.class.getName()).log(Level.WARNING, msg);
    52              Logger.getLogger(VulnerableSoftware.class.getName()).log(Level.FINE, null, ex);
    53              setName(cpe);
    54          }
    55      }
    56
    57      /**
    58       * <p>Parses a name attribute value, from the cpe.xml, into its
    59       * corresponding parts: vendor, product, version, revision.</p>
    60       * <p>Example:</p>
    61       * <code>&nbsp;&nbsp;&nbsp;cpe:/a:apache:struts:1.1:rc2</code>
    62       *
    63       * <p>Results in:</p> <ul> <li>Vendor: apache</li> <li>Product: struts</li>
    64       * <li>Version: 1.1</li> <li>Revision: rc2</li> </ul>
    65       *
    66       * @param cpeName the cpe name
    67       * @throws UnsupportedEncodingException should never be thrown...
    68       */
    69      @Override
    70      public void parseName(String cpeName) throws UnsupportedEncodingException {
    71          this.name = cpeName;
    72          if (cpeName != null && cpeName.length() > 7) {
    73              final String[] data = cpeName.substring(7).split(":");
    74              if (data.length >= 1) {
    75                  this.setVendor(URLDecoder.decode(data[0].replace("+", "%2B"), "UTF-8")); //.replaceAll("[_-]", " ")
    76                  if (data.length >= 2) {
    77                      this.setProduct(URLDecoder.decode(data[1].replace("+", "%2B"), "UTF-8")); //.replaceAll("[_-]", " ")
    78                      if (data.length >= 3) {
    79                          version = URLDecoder.decode(data[2].replace("+", "%2B"), "UTF-8");
    80                          if (data.length >= 4) {
    81                              revision = URLDecoder.decode(data[3].replace("+", "%2B"), "UTF-8");
    82                              if (data.length >= 5) {
    83                                  edition = URLDecoder.decode(data[4].replace("+", "%2B"), "UTF-8");
    84                              }
    85                          }
    86                      }
    87                  }
    88              }
    89          }
    90      }
    91      /**
    92       * If present, indicates that previous version are vulnerable.
    93       */
    94      private String previousVersion;
    95
    96      /**
    97       * Indicates if previous versions of this software are vulnerable.
    98       *
    99       * @return if previous versions of this software are vulnerable
    100      */
    101     public boolean hasPreviousVersion() {
    102         return previousVersion != null;
    103     }
    104
    105     /**
    106      * Get the value of previousVersion.
    107      *
    108      * @return the value of previousVersion
    109      */
    110     public String getPreviousVersion() {
    111         return previousVersion;
    112     }
    113
    114     /**
    115      * Set the value of previousVersion.
    116      *
    117      * @param previousVersion new value of previousVersion
    118      */
    119     public void setPreviousVersion(String previousVersion) {
    120         this.previousVersion = previousVersion;
    121     }
    122
    123     /**
    124      * Standard equals implementation to compare this VulnerableSoftware to
    125      * another object.
    126      *
    127      * @param obj the object to compare
    128      * @return whether or not the objects are equal
    129      */
    130     @Override
    131     public boolean equals(Object obj) {
    132         if (obj == null) {
    133             return false;
    134         }
    135         if (getClass() != obj.getClass()) {
    136             return false;
    137         }
    138         final VulnerableSoftware other = (VulnerableSoftware) obj;
    139         if ((this.getName() == null) ? (other.getName() != null) : !this.getName().equals(other.getName())) {
    140             return false;
    141         }
    142         return true;
    143     }
    144
    145     /**
    146      * Standard implementation of hashCode.
    147      *
    148      * @return the hashCode for the object
    149      */
    150     @Override
    151     public int hashCode() {
    152         int hash = 7;
    153         hash = 83 * hash + (this.getName() != null ? this.getName().hashCode() : 0);
    154         return hash;
    155     }
    156
    157     /**
    158      * Standard toString() implementation display the name and whether or not
    159      * previous versions are also affected.
    160      *
    161      * @return a string representation of the object
    162      */
    163     @Override
    164     public String toString() {
    165         return "VulnerableSoftware{ name=" + name + ", previousVersion=" + previousVersion + '}';
    166     }
    167
    168     /**
    169      * Implementation of the comparable interface.
    170      *
    171      * @param vs the VulnerableSoftware to compare
    172      * @return an integer indicating the ordering of the two objects
    173      */
    174     @Override
    175     public int compareTo(VulnerableSoftware vs) {
    176         int result = 0;
    177         final String[] left = this.getName().split(":");
    178         final String[] right = vs.getName().split(":");
    179         final int max = (left.length <= right.length) ? left.length : right.length;
    180         if (max > 0) {
    181             for (int i = 0; result == 0 && i < max; i++) {
    182                 final String[] subLeft = left[i].split("\\.");
    183                 final String[] subRight = right[i].split("\\.");
    184                 final int subMax = (subLeft.length <= subRight.length) ? subLeft.length : subRight.length;
    185                 if (subMax > 0) {
    186                     for (int x = 0; result == 0 && x < subMax; x++) {
    187                         if (isPositiveInteger(subLeft[x]) && isPositiveInteger(subRight[x])) {
    188                             final int iLeft = Integer.parseInt(subLeft[x]);
    189                             final int iRight = Integer.parseInt(subRight[x]);
    190                             if (iLeft != iRight) {
    191                                 if (iLeft > iRight) {
    192                                     result = 2;
    193                                 } else {
    194                                     result = -2;
    195                                 }
    196                             }
    197                         } else {
    198                             result = subLeft[x].compareToIgnoreCase(subRight[x]);
    199                         }
    200                     }
    201                     if (result == 0) {
    202                         if (subLeft.length > subRight.length) {
    203                             result = 2;
    204                         }
    205                         if (subRight.length > subLeft.length) {
    206                             result = -2;
    207                         }
    208                     }
    209                 } else {
    210                     result = left[i].compareToIgnoreCase(right[i]);
    211                 }
    212             }
    213             if (result == 0) {
    214                 if (left.length > right.length) {
    215                     result = 2;
    216                 }
    217                 if (right.length > left.length) {
    218                     result = -2;
    219                 }
    220             }
    221         } else {
    222             result = this.getName().compareToIgnoreCase(vs.getName());
    223         }
    224         return result;
    225     }
    226
    227     /**
    228      * Determines if the string passed in is a positive integer.
    229      *
    230      * @param str the string to test
    231      * @return true if the string only contains 0-9, otherwise false.
    232      */
    233     private static boolean isPositiveInteger(final String str) {
    234         if (str == null || str.isEmpty()) {
    235             return false;
    236         }
    237         for (int i = 0; i < str.length(); i++) {
    238             final char c = str.charAt(i);
    239             if (c < '0' || c > '9') {
    240                 return false;
    241             }
    242         }
    243         return true;
    244     }
    245     /**
    246      * The name of the cpe.
    247      */
    248     private String name;
    249
    250     /**
    251      * Get the value of name.
    252      *
    253      * @return the value of name
    254      */
    255     public String getName() {
    256         return name;
    257     }
    258
    259     /**
    260      * Set the value of name.
    261      *
    262      * @param name new value of name
    263      */
    264     public void setName(String name) {
    265         this.name = name;
    266     }
    267     /**
    268      * The product version number.
    269      */
    270     private String version;
    271
    272     /**
    273      * Get the value of version.
    274      *
    275      * @return the value of version
    276      */
    277     public String getVersion() {
    278         return version;
    279     }
    280
    281     /**
    282      * Set the value of version.
    283      *
    284      * @param version new value of version
    285      */
    286     public void setVersion(String version) {
    287         this.version = version;
    288     }
    289     /**
    290      * The product revision version.
    291      */
    292     private String revision;
    293
    294     /**
    295      * Get the value of revision.
    296      *
    297      * @return the value of revision
    298      */
    299     public String getRevision() {
    300         return revision;
    301     }
    302
    303     /**
    304      * Set the value of revision.
    305      *
    306      * @param revision new value of revision
    307      */
    308     public void setRevision(String revision) {
    309         this.revision = revision;
    310     }
    311     /**
    312      * The product edition.
    313      */
    314     private String edition;
    315
    316     /**
    317      * Get the value of edition.
    318      *
    319      * @return the value of edition
    320      */
    321     public String getEdition() {
    322         return edition;
    323     }
    324
    325     /**
    326      * Set the value of edition.
    327      *
    328      * @param edition new value of edition
    329      */
    330     public void setEdition(String edition) {
    331         this.edition = edition;
    332     }
    333 }