Coverage Report - org.owasp.dependencycheck.dependency.VulnerableSoftware
 
Classes in this File Line Coverage Branch Coverage Complexity
VulnerableSoftware
71%
72/101
80%
61/76
3.3
 
 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) 2012 Jeremy Long. All Rights Reserved.
 17  
  */
 18  
 package org.owasp.dependencycheck.dependency;
 19  
 
 20  
 import java.io.Serializable;
 21  
 import java.io.UnsupportedEncodingException;
 22  
 import java.net.URLDecoder;
 23  
 import org.owasp.dependencycheck.data.cpe.IndexEntry;
 24  
 import org.slf4j.Logger;
 25  
 import org.slf4j.LoggerFactory;
 26  
 
 27  
 /**
 28  
  * A record containing information about vulnerable software. This is referenced from a vulnerability.
 29  
  *
 30  
  * @author Jeremy Long
 31  
  */
 32  12845
 public class VulnerableSoftware extends IndexEntry implements Serializable, Comparable<VulnerableSoftware> {
 33  
 
 34  
     /**
 35  
      * The logger.
 36  
      */
 37  1
     private static final Logger LOGGER = LoggerFactory.getLogger(VulnerableSoftware.class);
 38  
     /**
 39  
      * The serial version UID.
 40  
      */
 41  
     private static final long serialVersionUID = 307319490326651052L;
 42  
 
 43  
     /**
 44  
      * Parse a CPE entry from the cpe string representation.
 45  
      *
 46  
      * @param cpe a cpe entry (e.g. cpe:/a:vendor:software:version)
 47  
      */
 48  
     public void setCpe(String cpe) {
 49  
         try {
 50  1159
             parseName(cpe);
 51  0
         } catch (UnsupportedEncodingException ex) {
 52  0
             LOGGER.warn("Character encoding is unsupported for CPE '{}'.", cpe);
 53  0
             LOGGER.debug("", ex);
 54  0
             setName(cpe);
 55  1159
         }
 56  1159
     }
 57  
 
 58  
     /**
 59  
      * <p>
 60  
      * Parses a name attribute value, from the cpe.xml, into its corresponding parts: vendor, product, version, update.</p>
 61  
      * <p>
 62  
      * Example:</p>
 63  
      * <code>&nbsp;&nbsp;&nbsp;cpe:/a:apache:struts:1.1:rc2</code>
 64  
      *
 65  
      * <p>
 66  
      * Results in:</p> <ul> <li>Vendor: apache</li> <li>Product: struts</li>
 67  
      * <li>Version: 1.1</li> <li>Revision: rc2</li> </ul>
 68  
      *
 69  
      * @param cpeName the cpe name
 70  
      * @throws UnsupportedEncodingException should never be thrown...
 71  
      */
 72  
     @Override
 73  
     public void parseName(String cpeName) throws UnsupportedEncodingException {
 74  1797
         this.name = cpeName;
 75  1797
         if (cpeName != null && cpeName.length() > 7) {
 76  1788
             final String[] data = cpeName.substring(7).split(":");
 77  1788
             if (data.length >= 1) {
 78  1788
                 this.setVendor(urlDecode(data[0]));
 79  
             }
 80  1788
             if (data.length >= 2) {
 81  1787
                 this.setProduct(urlDecode(data[1]));
 82  
             }
 83  1788
             if (data.length >= 3) {
 84  1787
                 version = urlDecode(data[2]);
 85  
             }
 86  1788
             if (data.length >= 4) {
 87  232
                 update = urlDecode(data[3]);
 88  
             }
 89  1788
             if (data.length >= 5) {
 90  0
                 edition = urlDecode(data[4]);
 91  
             }
 92  
         }
 93  1797
     }
 94  
     /**
 95  
      * If present, indicates that previous version are vulnerable.
 96  
      */
 97  
     private String previousVersion;
 98  
 
 99  
     /**
 100  
      * Indicates if previous versions of this software are vulnerable.
 101  
      *
 102  
      * @return if previous versions of this software are vulnerable
 103  
      */
 104  
     public boolean hasPreviousVersion() {
 105  0
         return previousVersion != null;
 106  
     }
 107  
 
 108  
     /**
 109  
      * Get the value of previousVersion.
 110  
      *
 111  
      * @return the value of previousVersion
 112  
      */
 113  
     public String getPreviousVersion() {
 114  0
         return previousVersion;
 115  
     }
 116  
 
 117  
     /**
 118  
      * Set the value of previousVersion.
 119  
      *
 120  
      * @param previousVersion new value of previousVersion
 121  
      */
 122  
     public void setPreviousVersion(String previousVersion) {
 123  15
         this.previousVersion = previousVersion;
 124  15
     }
 125  
 
 126  
     /**
 127  
      * Standard equals implementation to compare this VulnerableSoftware to another object.
 128  
      *
 129  
      * @param obj the object to compare
 130  
      * @return whether or not the objects are equal
 131  
      */
 132  
     @Override
 133  
     public boolean equals(Object obj) {
 134  4
         if (obj == null) {
 135  0
             return false;
 136  
         }
 137  4
         if (getClass() != obj.getClass()) {
 138  0
             return false;
 139  
         }
 140  4
         final VulnerableSoftware other = (VulnerableSoftware) obj;
 141  4
         if ((this.name == null) ? (other.getName() != null) : !this.name.equals(other.getName())) {
 142  1
             return false;
 143  
         }
 144  3
         return true;
 145  
     }
 146  
 
 147  
     /**
 148  
      * Standard implementation of hashCode.
 149  
      *
 150  
      * @return the hashCode for the object
 151  
      */
 152  
     @Override
 153  
     public int hashCode() {
 154  149
         int hash = 7;
 155  149
         hash = 83 * hash + (this.name != null ? this.name.hashCode() : 0);
 156  149
         return hash;
 157  
     }
 158  
 
 159  
     /**
 160  
      * Standard toString() implementation display the name and whether or not previous versions are also affected.
 161  
      *
 162  
      * @return a string representation of the object
 163  
      */
 164  
     @Override
 165  
     public String toString() {
 166  0
         return "VulnerableSoftware{" + name + "[" + previousVersion + "]}";
 167  
     }
 168  
 
 169  
     /**
 170  
      * Implementation of the comparable interface.
 171  
      *
 172  
      * @param vs the VulnerableSoftware to compare
 173  
      * @return an integer indicating the ordering of the two objects
 174  
      */
 175  
     @Override
 176  
     public int compareTo(VulnerableSoftware vs) {
 177  11074
         int result = 0;
 178  11074
         final String[] left = this.name.split(":");
 179  11074
         final String[] right = vs.getName().split(":");
 180  11074
         final int max = (left.length <= right.length) ? left.length : right.length;
 181  11074
         if (max > 0) {
 182  66639
             for (int i = 0; result == 0 && i < max; i++) {
 183  55565
                 final String[] subLeft = left[i].split("(\\.|-)");
 184  55565
                 final String[] subRight = right[i].split("(\\.|-)");
 185  55565
                 final int subMax = (subLeft.length <= subRight.length) ? subLeft.length : subRight.length;
 186  55565
                 if (subMax > 0) {
 187  127140
                     for (int x = 0; result == 0 && x < subMax; x++) {
 188  71584
                         if (isPositiveInteger(subLeft[x]) && isPositiveInteger(subRight[x])) {
 189  
                             try {
 190  25239
                                 result = Long.valueOf(subLeft[x]).compareTo(Long.valueOf(subRight[x]));
 191  0
                             } catch (NumberFormatException ex) {
 192  
                                 //ignore the exception - they obviously aren't numbers
 193  0
                                 if (!subLeft[x].equalsIgnoreCase(subRight[x])) {
 194  0
                                     result = subLeft[x].compareToIgnoreCase(subRight[x]);
 195  
                                 }
 196  25239
                             }
 197  
                         } else {
 198  46345
                             result = subLeft[x].compareToIgnoreCase(subRight[x]);
 199  
                         }
 200  
                     }
 201  55556
                     if (result == 0) {
 202  44814
                         if (subLeft.length > subRight.length) {
 203  156
                             result = 2;
 204  
                         }
 205  44814
                         if (subRight.length > subLeft.length) {
 206  53
                             result = -2;
 207  
                         }
 208  
                     }
 209  
                 } else {
 210  9
                     result = left[i].compareToIgnoreCase(right[i]);
 211  
                 }
 212  
             }
 213  11074
             if (result == 0) {
 214  117
                 if (left.length > right.length) {
 215  68
                     result = 2;
 216  
                 }
 217  117
                 if (right.length > left.length) {
 218  10
                     result = -2;
 219  
                 }
 220  
             }
 221  
         } else {
 222  0
             result = this.getName().compareToIgnoreCase(vs.getName());
 223  
         }
 224  11074
         return result;
 225  
     }
 226  
 
 227  
     /**
 228  
      * Determines if the string passed in is a positive integer.
 229  
      * To be counted as a positive integer, the string must only contain 0-9
 230  
      * and must not have any leading zeros (though "0" is a valid positive
 231  
      * integer).
 232  
      *
 233  
      * @param str the string to test
 234  
      * @return true if the string only contains 0-9, otherwise false.
 235  
      */
 236  
     static boolean isPositiveInteger(final String str) {
 237  97150
         if (str == null || str.isEmpty()) {
 238  14
             return false;
 239  
         }
 240  
 
 241  
         // numbers with leading zeros should not be treated as numbers
 242  
         // (e.g. when comparing "01" <-> "1")
 243  97136
         if (str.charAt(0) == '0' && str.length() > 1) {
 244  1153
             return false;
 245  
         }
 246  
 
 247  153422
         for (int i = 0; i < str.length(); i++) {
 248  102622
             final char c = str.charAt(i);
 249  102622
             if (c < '0' || c > '9') {
 250  45183
                 return false;
 251  
             }
 252  
         }
 253  50800
         return true;
 254  
     }
 255  
     /**
 256  
      * The name of the cpe.
 257  
      */
 258  
     private String name;
 259  
 
 260  
     /**
 261  
      * Get the value of name.
 262  
      *
 263  
      * @return the value of name
 264  
      */
 265  
     public String getName() {
 266  11121
         return name;
 267  
     }
 268  
 
 269  
     /**
 270  
      * Set the value of name.
 271  
      *
 272  
      * @param name new value of name
 273  
      */
 274  
     public void setName(String name) {
 275  0
         this.name = name;
 276  0
     }
 277  
     /**
 278  
      * The product version number.
 279  
      */
 280  
     private String version;
 281  
 
 282  
     /**
 283  
      * Get the value of version.
 284  
      *
 285  
      * @return the value of version
 286  
      */
 287  
     public String getVersion() {
 288  2329
         return version;
 289  
     }
 290  
 
 291  
     /**
 292  
      * Set the value of version.
 293  
      *
 294  
      * @param version new value of version
 295  
      */
 296  
     public void setVersion(String version) {
 297  0
         this.version = version;
 298  0
     }
 299  
     /**
 300  
      * The product update version.
 301  
      */
 302  
     private String update;
 303  
 
 304  
     /**
 305  
      * Get the value of update.
 306  
      *
 307  
      * @return the value of update
 308  
      */
 309  
     public String getUpdate() {
 310  1490
         return update;
 311  
     }
 312  
 
 313  
     /**
 314  
      * Set the value of update.
 315  
      *
 316  
      * @param update new value of update
 317  
      */
 318  
     public void setUpdate(String update) {
 319  0
         this.update = update;
 320  0
     }
 321  
     /**
 322  
      * The product edition.
 323  
      */
 324  
     private String edition;
 325  
 
 326  
     /**
 327  
      * Get the value of edition.
 328  
      *
 329  
      * @return the value of edition
 330  
      */
 331  
     public String getEdition() {
 332  0
         return edition;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Set the value of edition.
 337  
      *
 338  
      * @param edition new value of edition
 339  
      */
 340  
     public void setEdition(String edition) {
 341  0
         this.edition = edition;
 342  0
     }
 343  
 
 344  
     /**
 345  
      * Replaces '+' with '%2B' and then URL Decodes the string attempting first UTF-8, then ASCII, then default.
 346  
      *
 347  
      * @param string the string to URL Decode
 348  
      * @return the URL Decoded string
 349  
      */
 350  
     private String urlDecode(String string) {
 351  5594
         final String text = string.replace("+", "%2B");
 352  
         String result;
 353  
         try {
 354  5594
             result = URLDecoder.decode(text, "UTF-8");
 355  0
         } catch (UnsupportedEncodingException ex) {
 356  
             try {
 357  0
                 result = URLDecoder.decode(text, "ASCII");
 358  0
             } catch (UnsupportedEncodingException ex1) {
 359  0
                 result = defaultUrlDecode(text);
 360  0
             }
 361  5594
         }
 362  5594
         return result;
 363  
     }
 364  
 
 365  
     /**
 366  
      * Call {@link java.net.URLDecoder#decode(String)} to URL decode using the default encoding.
 367  
      *
 368  
      * @param text www-form-encoded URL to decode
 369  
      * @return the newly decoded String
 370  
      */
 371  
     @SuppressWarnings("deprecation")
 372  
     private String defaultUrlDecode(final String text) {
 373  0
         return URLDecoder.decode(text);
 374  
     }
 375  
 }