Skip to content

Commit

Permalink
Add NewJarManifest function to retrieve MANIFEST from a JAR file (#153)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Mikusa <dmikusa@vmware.com>
  • Loading branch information
SaschaSchwarze0 and Daniel Mikusa authored Mar 22, 2022
1 parent 35338e3 commit 7c79a7a
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 16 deletions.
5 changes: 3 additions & 2 deletions init_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2020 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,7 +38,8 @@ func TestUnit(t *testing.T) {
suite("JavaSecurityProperties", testJavaSecurityProperties)
suite("JDK", testJDK)
suite("JRE", testJRE)
suite("Manifest", testManifest)
suite("NewManifest", testNewManifest)
suite("NewManifestFromJAR", testNewManifestFromJAR)
suite("MavenJARListing", testMavenJARListing)
suite("Versions", testVersions)
suite("JVMVersions", testJVMVersion)
Expand Down
52 changes: 40 additions & 12 deletions manifest.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2020 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,9 +17,12 @@
package libjvm

import (
"archive/zip"
"bytes"
"errors"
"fmt"
"io/ioutil"
"io"
"io/fs"
"os"
"path/filepath"

Expand All @@ -39,31 +42,56 @@ func NewManifest(applicationPath string) (*properties.Properties, error) {
}
defer in.Close()

b, err := ioutil.ReadAll(in)
return loadManifest(in, file)
}

// NewManifestFromJAR reads the META-INF/MANIFEST.MF from a JAR file if it exists, normalizing it into the
// standard properties form.
func NewManifestFromJAR(jarFilePath string) (*properties.Properties, error) {
// open the JAR file
jarFile, err := zip.OpenReader(jarFilePath)
if err != nil {
return nil, fmt.Errorf("unable to read file %s\n%w", jarFilePath, err)
}
defer jarFile.Close()

// look for the MANIFEST
manifestFile, err := jarFile.Open("META-INF/MANIFEST.MF")
if err != nil {
return nil, fmt.Errorf("unable to read %s\n%w", file, err)
if errors.Is(err, fs.ErrNotExist) {
return &properties.Properties{}, nil
}
return nil, fmt.Errorf("unable to read MANIFEST.MF in %s\n%w", jarFilePath, err)
}

// The full grammar for manifests can be found here:
// https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JARManifest
return loadManifest(manifestFile, jarFilePath)
}

func loadManifest(reader io.Reader, source string) (*properties.Properties, error) {
// read the MANIFEST
manifestBytes, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("unable to read MANIFEST.MF in %s\n%w", source, err)
}

// Convert Windows style line endings to UNIX
b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n"))
manifestBytes = bytes.ReplaceAll(manifestBytes, []byte("\r\n"), []byte("\n"))

// The spec allows newlines to be single carriage-returns
// this is a legacy line ending only supported on System 9
// and before.
b = bytes.ReplaceAll(b, []byte("\r"), []byte("\n"))
manifestBytes = bytes.ReplaceAll(manifestBytes, []byte("\r"), []byte("\n"))

// The spec only allowed for line lengths of 78 bytes.
// All lines are blank, start a property name or are
// a continuation of the previous lines (indicated by a leading space).
b = bytes.ReplaceAll(b, []byte("\n "), []byte{})
manifestBytes = bytes.ReplaceAll(manifestBytes, []byte("\n "), []byte{})

p, err := properties.Load(b, properties.UTF8)
// parse the MANIFEST
manifest, err := properties.Load(manifestBytes, properties.UTF8)
if err != nil {
return nil, fmt.Errorf("unable to parse properties from %s\n%w", file, err)
return nil, fmt.Errorf("unable to parse properties in MANIFEST.MF in %s\n%w", source, err)
}

return p, nil
return manifest, nil
}
60 changes: 58 additions & 2 deletions manifest_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2020 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,8 @@
package libjvm_test

import (
"archive/zip"
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -28,7 +30,7 @@ import (
"github.com/paketo-buildpacks/libjvm"
)

func testManifest(t *testing.T, context spec.G, it spec.S) {
func testNewManifest(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

Expand Down Expand Up @@ -89,3 +91,57 @@ Main-Class: org.springframework.boot.loader.JarLauncher
})

}

func testNewManifestFromJAR(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
path string

createJARFile = func(fileName string, mainClass string) string {
fullPath := filepath.Join(path, fileName)

archive, err := os.Create(fullPath)
Expect(err).NotTo(HaveOccurred())
defer archive.Close()
zipWriter := zip.NewWriter(archive)

if mainClass != "" {
manifestWriter, err := zipWriter.Create("META-INF/MANIFEST.MF")
Expect(err).NotTo(HaveOccurred())
manifestWriter.Write([]byte(fmt.Sprintf("Main-Class: %s", mainClass)))
}
zipWriter.Close()

return fullPath
}
)

it.Before(func() {
var err error
path, err = ioutil.TempDir("", "jardir")
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
Expect(os.RemoveAll(path)).To(Succeed())
})

it("returns empty manifest if the JAR file doesn't contain a MANIFEST", func() {
filePath := createJARFile("test.jar", "")

m, err := libjvm.NewManifestFromJAR(filePath)
Expect(err).NotTo(HaveOccurred())
Expect(m.Len()).To(Equal(0))
})

it("returns a filled manifest if the JAR file contains a MANIFEST", func() {
filePath := createJARFile("test.jar", "test.Main")

m, err := libjvm.NewManifestFromJAR(filePath)
Expect(err).NotTo(HaveOccurred())
Expect(m.Len()).To(Equal(1))
mainClass, ok := m.Get("Main-Class")
Expect(ok).To(BeTrue())
Expect(mainClass).To(Equal("test.Main"))
})
}

0 comments on commit 7c79a7a

Please sign in to comment.