Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: gnewton/jargo
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: mcoops/jargo
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 9 commits
  • 6 files changed
  • 1 contributor

Commits on Mar 23, 2021

  1. Recursively find all files, jars within jars

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Mar 23, 2021
    Copy the full SHA
    d30bd4a View commit details
  2. fix: incorrect err check

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Mar 23, 2021
    Copy the full SHA
    ea1d96d View commit details

Commits on Apr 1, 2021

  1. Copy the full SHA
    3d1671c View commit details
  2. fix file perms

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Apr 1, 2021
    Copy the full SHA
    f9604e3 View commit details

Commits on Apr 7, 2021

  1. add in .zip internal files as well

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Apr 7, 2021
    Copy the full SHA
    3631bd3 View commit details

Commits on Apr 10, 2021

  1. version parsing: more accurate

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Apr 10, 2021
    Copy the full SHA
    26a02fc View commit details

Commits on Apr 21, 2021

  1. panic: fix error when the jar isn't a valid archive

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Apr 21, 2021
    Copy the full SHA
    3a62b19 View commit details
  2. spelling: fix error spelling

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Apr 21, 2021
    Copy the full SHA
    8a7c97c View commit details

Commits on Sep 27, 2021

  1. manifestMap: if we cannot parse the line skip it

    Signed-off-by: mcoops <cooper.d.mark@gmail.com>
    mcoops committed Sep 27, 2021
    Copy the full SHA
    2eb4086 View commit details
Showing with 184 additions and 9 deletions.
  1. +1 −0 .gitignore
  2. +31 −0 cmd/main.go
  3. +5 −0 go.mod
  4. +8 −0 go.sum
  5. +137 −7 jar.go
  6. +2 −2 jar_test.go
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -22,3 +22,4 @@ _testmain.go
*.exe
*.test
*.prof
.vscode/launch.json
31 changes: 31 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"flag"
"fmt"
"log"
"os"

"github.com/mcoops/jargo"
)

func main() {
filename := flag.String("jar", "", "The Jar file to scan")

flag.Parse()

if _, err := os.Stat(*filename); os.IsNotExist(err) {
log.Fatalf("Valid file must be specified with -jar, not found: %s", *filename)
}

jar, err := jargo.GetJarInfo(*filename)

if err != nil {
fmt.Println(err)
return
}
for _, f := range jar.Files {
fmt.Println(f)
}

}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/mcoops/jargo

go 1.16

require github.com/vifraa/gopom v0.1.0 // indirect
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vifraa/gopom v0.1.0 h1:v897eVxf6lflkEXzPmKbo4YhX2oS/LGjz7cqjWnSmCU=
github.com/vifraa/gopom v0.1.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
144 changes: 137 additions & 7 deletions jar.go
Original file line number Diff line number Diff line change
@@ -4,10 +4,17 @@ import (
"archive/zip"
"bufio"
"bytes"
"encoding/xml"
"errors"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/vifraa/gopom"
)

type Manifest map[string]string
@@ -17,13 +24,38 @@ type JarInfo struct {
Files []string
}

var JarFilter = [...]string{".jar", ".war", ".hpi", ".rar", ".ear", ".zip"}

var jarVersionRegex *regexp.Regexp

const MANIFEST_FULL_NAME = "META-INF/MANIFEST.MF"

func init() {
jarVersionRegex = regexp.MustCompile(".*?-([0-9\\.][\\w0-9\\.-]*)\\.jar")
}

// https://codereview.stackexchange.com/questions/191238/return-unique-items-in-a-go-slice/192954#192954
func unique(slice []string) []string {
// create a map with all the values as key
uniqMap := make(map[string]struct{})
for _, v := range slice {
uniqMap[v] = struct{}{}
}

// turn the map keys into a slice
uniqSlice := make([]string, 0, len(uniqMap))
for v := range uniqMap {
uniqSlice = append(uniqSlice, v)
}
return uniqSlice
}

// GetManifest extracts the manifest info from a Java JAR file
// It takes as parameter the path to the jar file of interest
// It returns a pointer to a Manifest (map[string]string) which is the key:values pairs from the META-INF/MANIFEST.MF file
func GetManifest(filename string) (*Manifest, error) {
jar, err := readFromFile(filename, false)

if err != nil {
return nil, err
}
@@ -36,9 +68,26 @@ func GetManifest(filename string) (*Manifest, error) {
// It extracts an array of the filenames in the JAR file
// It returns a pointer to a JarInfo struct
func GetJarInfo(filename string) (*JarInfo, error) {
return readFromFile(filename, true)
jar, err := readFromFile(filename, true)

if err != nil {
return nil, errors.New("Error processing " + filename + " " + err.Error())
}

first := jar.Files[0]
// not great, but given it's recursive we're generally going to get the top level jar accidentally.
if strings.Contains(first, strings.Replace(filename, filepath.Ext(filename), "", 1)) {
jar.Files = jar.Files[1:]
}

// unique it
jar.Files = unique(jar.Files)

return jar, err
}

var gJar JarInfo

func readFromFile(filename string, fullJar bool) (*JarInfo, error) {
var err error
var file *os.File
@@ -57,10 +106,28 @@ func readFromFile(filename string, fullJar bool) (*JarInfo, error) {
if r, err = zip.NewReader(file, fi.Size()); err != nil {
return nil, err
}
return readFromReader(r, fullJar)
return readFromReader(r, fullJar, filename)
}

func inExtFilter(name string) bool {
for _, ext := range JarFilter {
if strings.HasSuffix(name, ext) {
return true
}
}
return false
}

func cleanJarName(name string) string {
out := strings.TrimPrefix(name, "WEB-INF/lib/")
out = strings.TrimPrefix(out, "WEB-INF/")
out = strings.TrimPrefix(out, "detached-plugins/")
out = strings.Replace(out, filepath.Ext(out), "", 1)

return out
}

func readFromReader(r *zip.Reader, fullJar bool) (*JarInfo, error) {
func readFromReader(r *zip.Reader, fullJar bool, jarName string) (*JarInfo, error) {
var (
part []byte
prefix bool
@@ -74,8 +141,48 @@ func readFromReader(r *zip.Reader, fullJar bool) (*JarInfo, error) {
lineNumber := -1
for _, f := range r.File {
if fullJar {
jar.Files = append(jar.Files, f.Name)

if inExtFilter(f.Name) {
// jar.Files = append(jar.Files, f.Name)
rf, err := f.Open()
if err != nil { // just in case
continue
}

body, _ := ioutil.ReadAll(rf)
zr, err := zip.NewReader(bytes.NewReader(body), f.FileInfo().Size())
if err == nil { // pretty much ignore errors
rJar, err := readFromReader(zr, true, f.Name)
if err == nil {
jar.Files = append(jar.Files, rJar.Files...)
}
}
} else if strings.HasPrefix(f.Name, "META-INF") && strings.HasSuffix(f.Name, "pom.xml") {
// get the version from MANIFEST.MF

name := strings.Replace(f.Name, "META-INF/maven/", "", 1)

rf, err := f.Open()
if err != nil {
continue
}
body, _ := ioutil.ReadAll(rf)
var parsedPom gopom.Project

err = xml.Unmarshal(body, &parsedPom)
if err != nil {
continue
}

// we could potentially add jarname to the path, but makes searching a pain later
if parsedPom.Version == "" {
jar.Files = append(jar.Files, strings.Replace(name, "/pom.xml", "@"+parsedPom.Parent.Version, 1))
} else {
jar.Files = append(jar.Files, strings.Replace(name, "/pom.xml", "@"+parsedPom.Version, 1))
}
}
}

if f.Name == MANIFEST_FULL_NAME {
rc, err := f.Open()
if err != nil {
@@ -109,9 +216,31 @@ func readFromReader(r *zip.Reader, fullJar bool) (*JarInfo, error) {
err = nil
}
rc.Close()
jar.Manifest = makeManifestMap(lines)
}
}

// if we're here and jar.Files is empty, means no pom.xml, so log something
if jar.Files == nil {
// use the jarfilename
if jarName != "" {
v := jarVersionRegex.FindStringSubmatch(jarName)
n := cleanJarName(jarName)
var ver string = ""
if v == nil { // just go off last "-"
idx := strings.LastIndex(n, "-")
if idx != -1 {
ver = n[idx+1:]
}
} else {
ver = v[1]
}

n = strings.Replace(n, "-"+ver, "@"+ver, 1)
jar.Files = append(jar.Files, n)
}
}
jar.Manifest = makeManifestMap(lines)

return jar, nil
}

@@ -120,8 +249,9 @@ func makeManifestMap(lines []string) *Manifest {

for _, line := range lines {
i := strings.Index(line, ":")
if i == -1 {
log.Println("Not properties file?? This line missing colon (:): " + line)
if i == -1 || i == 0 {
// log.Println("Not properties file?? This line missing colon (:): " + line)
continue
}
key := strings.TrimSpace(line[0:i])
value := strings.TrimSpace(line[i+1:])
4 changes: 2 additions & 2 deletions jar_test.go
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ func TestMain(m *testing.M) {
}

func TestValidJarFile_JarInfo(t *testing.T) {
_, err := readFromReader(getReader(), true)
_, err := readFromReader(getReader(), true, "")
if err != nil {
t.FailNow()
}
@@ -39,7 +39,7 @@ func TestMissingJarFile_JarInfo(t *testing.T) {
}

func TestValidJarFile_JarManifest(t *testing.T) {
manifest, err := readFromReader(getReader(), false)
manifest, err := readFromReader(getReader(), false, "")
if err != nil {
log.Println(err)
t.FailNow()