github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/ant/ant.go (about)

     1  package ant
     2  
     3  import (
     4  	"archive/zip"
     5  	"bufio"
     6  	"encoding/xml"
     7  	"errors"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/apex/log"
    14  	"github.com/bmatcuk/doublestar"
    15  	"github.com/gnewton/jargo"
    16  
    17  	"github.com/fossas/fossa-cli/buildtools/maven"
    18  	"github.com/fossas/fossa-cli/graph"
    19  	"github.com/fossas/fossa-cli/pkg"
    20  )
    21  
    22  func Graph(dir string) (graph.Deps, error) {
    23  	jarFilePaths, err := doublestar.Glob(filepath.Join(dir, "*.jar"))
    24  	if err != nil {
    25  		return graph.Deps{}, err
    26  	}
    27  
    28  	log.Debugf("Running Ant analysis: %#v", jarFilePaths)
    29  
    30  	// traverse through libdir and and resolve jars
    31  	var imports []pkg.Import
    32  	depGraph := make(map[pkg.ID]pkg.Package)
    33  	for _, jarFilePath := range jarFilePaths {
    34  		locator, err := locatorFromJar(jarFilePath)
    35  		if err == nil {
    36  			imports = append(imports, pkg.Import{
    37  				Resolved: locator,
    38  			})
    39  			depGraph[locator] = pkg.Package{
    40  				ID: locator,
    41  			}
    42  		} else {
    43  			log.Warnf("unable to resolve Jar: %s", jarFilePath)
    44  		}
    45  	}
    46  
    47  	return graph.Deps{
    48  		Direct:     imports,
    49  		Transitive: depGraph,
    50  	}, nil
    51  }
    52  
    53  // locatorFromJar resolves a locator from a .jar file by inspecting its contents.
    54  func locatorFromJar(path string) (pkg.ID, error) {
    55  	log.Debugf("processing locator from Jar: %s", path)
    56  
    57  	info, err := jargo.GetJarInfo(path)
    58  	if err == nil {
    59  		// first, attempt to resolve a pomfile from the META-INF directory
    60  		var pomFilePath string
    61  		for _, file := range info.Files {
    62  			if strings.HasPrefix(file, "META-INF") && strings.HasSuffix(file, "pom.xml") && (pomFilePath == "" || len(pomFilePath) > len(file)) {
    63  				pomFilePath = file
    64  			}
    65  		}
    66  
    67  		pomFile, err := getPOMFromJar(pomFilePath)
    68  		if err == nil {
    69  			log.Debugf("resolving locator from pom: %s", pomFilePath)
    70  			return pkg.ID{
    71  				Type:     pkg.Maven,
    72  				Name:     pomFile.GroupID + ":" + pomFile.ArtifactID,
    73  				Revision: pomFile.Version,
    74  			}, nil
    75  		} else {
    76  			log.Debugf("%s", err)
    77  		}
    78  
    79  		// failed to decode pom file, fall back to META-INF
    80  		manifest := *info.Manifest
    81  		if manifest["Bundle-SymbolicName"] != "" && manifest["Implementation-Version"] != "" {
    82  			log.Debugf("resolving locator from META-INF: %s", info.Manifest)
    83  			return pkg.ID{
    84  				Type:     pkg.Maven,
    85  				Name:     manifest["Bundle-SymbolicName"], // TODO: identify GroupId
    86  				Revision: manifest["Implementation-Version"],
    87  			}, nil
    88  		}
    89  	}
    90  
    91  	// fall back to parsing file name
    92  	re := regexp.MustCompile("(-sources|-javadoc)?.jar$")
    93  	nameParts := strings.Split(re.ReplaceAllString(filepath.Base(path), ""), "-")
    94  	lenNameParts := len(nameParts)
    95  
    96  	var parsedProjectName string
    97  	var parsedRevisionName string
    98  
    99  	if lenNameParts == 1 {
   100  		parsedProjectName = nameParts[0]
   101  	} else if lenNameParts > 1 {
   102  		parsedProjectName = strings.Join(nameParts[0:lenNameParts-1], "-")
   103  		parsedRevisionName = nameParts[lenNameParts-1]
   104  	}
   105  
   106  	if parsedProjectName == "" {
   107  		return pkg.ID{}, errors.New("unable to parse jar file")
   108  	}
   109  
   110  	return pkg.ID{
   111  		Type:     pkg.Maven,
   112  		Name:     parsedProjectName,
   113  		Revision: parsedRevisionName,
   114  	}, nil
   115  }
   116  
   117  func getPOMFromJar(path string) (maven.Manifest, error) {
   118  	var pomFile maven.Manifest
   119  
   120  	log.Debugf(path)
   121  	if path == "" {
   122  		return pomFile, errors.New("invalid POM path specified")
   123  	}
   124  
   125  	jarFile, err := os.Open(path)
   126  	if err != nil {
   127  		return pomFile, err
   128  	}
   129  
   130  	defer jarFile.Close()
   131  
   132  	zfi, err := jarFile.Stat()
   133  	if err != nil {
   134  		return pomFile, err
   135  	}
   136  
   137  	zr, err := zip.NewReader(jarFile, zfi.Size())
   138  	if err != nil {
   139  		return pomFile, err
   140  	}
   141  
   142  	for _, f := range zr.File {
   143  		// decode a single pom.xml directly from jar
   144  		if f.Name == path {
   145  			rc, err := f.Open()
   146  			if err != nil {
   147  				return pomFile, err
   148  			}
   149  			defer rc.Close()
   150  
   151  			reader := bufio.NewReader(rc)
   152  			decoder := xml.NewDecoder(reader)
   153  
   154  			if err := decoder.Decode(&pomFile); err != nil {
   155  				return pomFile, err
   156  			}
   157  
   158  			return pomFile, nil
   159  		}
   160  	}
   161  
   162  	return pomFile, errors.New("unable to parse POM from Jar")
   163  }