Task 321: .JAR File Format
Task 321: .JAR File Format
JAR File Format Specifications
The .JAR file format is a platform-independent archive format used primarily for Java applications and libraries. It is based on the ZIP file format and includes a Java-specific manifest file for metadata. A JAR file is essentially a ZIP archive that must contain a META-INF/MANIFEST.MF file, which follows an RFC822-style header format for attributes like Manifest-Version, Main-Class, and others. The underlying ZIP structure provides the file system organization, with components like local file headers, central directory headers, and an end of central directory record.
- List of all properties of this file format intrinsic to its file system:
Based on the ZIP file format specifications, the key intrinsic properties are derived from the end of central directory record, which defines the overall archive structure. These are global properties that describe the file system's layout, entry counts, and metadata. For JAR files, these are inherited from ZIP, with the addition that the archive must conform to JAR-specific rules (e.g., UTF-8 encoding for names, manifest presence). The properties are:
- End of Central Directory Signature: Fixed value of 0x06054b50 (PK\005\006 in ASCII), 4 bytes.
- Number of this disk: The disk number containing the current part of the archive (usually 0 for non-spanned archives), 2 bytes.
- Number of the disk with the start of the central directory: The disk where the central directory begins (usually 0), 2 bytes.
- Total number of entries in the central directory on this disk: Number of file entries on the current disk, 2 bytes.
- Total number of entries in the central directory: Total number of file entries in the entire archive, 2 bytes.
- Size of the central directory: The size in bytes of the central directory records, 4 bytes.
- Offset of start of central directory with respect to the starting disk number: Byte offset from the start of the archive to the central directory, 4 bytes.
- ZIP file comment length: Length of the optional archive comment, 2 bytes.
- ZIP file comment: Optional comment string (variable length, up to 65535 bytes).
These properties are intrinsic as they define the archive's file system boundaries, multiplicity, and navigation without depending on specific content.
- Two direct download links for .JAR files:
- https://repo1.maven.org/maven2/junit/junit/4.13.2/junit-4.13.2.jar (JUnit library JAR).
- https://repo1.maven.org/maven2/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar (Gson library JAR).
- Ghost blog embedded HTML JavaScript for drag-and-drop .JAR file to dump properties:
This is an embeddable HTML snippet with JavaScript that allows dragging and dropping a .JAR file. It reads the file as an ArrayBuffer, scans from the end for the end of central directory signature, parses the properties, and displays them on the screen.
- Python class for opening, decoding, reading, writing, and printing .JAR properties:
This class manually parses the .JAR (ZIP) file to extract and print the properties. For writing, it provides a basic method to update the comment (as an example of write capability; full write would require rewriting the archive).
import struct
import os
class JarParser:
def __init__(self, filename):
self.filename = filename
self.properties = {}
def read_properties(self):
with open(self.filename, 'rb') as f:
data = f.read()
# Scan from end for signature 0x06054b50
pos = len(data) - 22
while pos >= 0:
if struct.unpack_from('<I', data, pos)[0] == 0x06054b50:
break
pos -= 1
if pos < 0:
raise ValueError('Invalid JAR/ZIP file: End of central directory signature not found.')
# Unpack fields (little-endian)
(_, this_disk, cd_start_disk, entries_this_disk, total_entries,
cd_size, cd_offset, comment_len) = struct.unpack_from('<IHHHHIIH', data, pos)
comment = data[pos + 22 : pos + 22 + comment_len].decode('utf-8', errors='ignore')
self.properties = {
'End of Central Directory Signature': hex(0x06054b50),
'Number of this disk': this_disk,
'Number of the disk with the start of the central directory': cd_start_disk,
'Total number of entries in the central directory on this disk': entries_this_disk,
'Total number of entries in the central directory': total_entries,
'Size of the central directory': cd_size,
'Offset of start of central directory': cd_offset,
'ZIP file comment length': comment_len,
'ZIP file comment': comment,
}
def print_properties(self):
if not self.properties:
self.read_properties()
for key, value in self.properties.items():
print(f'{key}: {value}')
def write_comment(self, new_comment):
# Example write: update comment (assumes no ZIP64, simple case)
with open(self.filename, 'r+b') as f:
data = f.read()
pos = len(data) - 22
while pos >= 0:
if struct.unpack_from('<I', data, pos)[0] == 0x06054b50:
break
pos -= 1
if pos < 0:
raise ValueError('Cannot write: Invalid file.')
old_comment_len = struct.unpack_from('<H', data, pos + 20)[0]
new_comment_bytes = new_comment.encode('utf-8')
new_data = data[:pos + 22] + new_comment_bytes
# Update comment length
struct.pack_into('<H', new_data, pos + 20, len(new_comment_bytes))
with open(self.filename, 'wb') as f:
f.write(new_data)
# Example usage:
# parser = JarParser('example.jar')
# parser.print_properties()
# parser.write_comment('New comment')
- Java class for opening, decoding, reading, writing, and printing .JAR properties:
This class uses DataInputStream for parsing. For writing, it updates the comment as an example.
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;
public class JarParser {
private String filename;
private Map<String, Object> properties = new HashMap<>();
public JarParser(String filename) {
this.filename = filename;
}
public void readProperties() throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filename));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int pos = data.length - 22;
while (pos >= 0) {
if (buffer.getInt(pos) == 0x06054b50) {
break;
}
pos--;
}
if (pos < 0) {
throw new IOException("Invalid JAR/ZIP file: End of central directory signature not found.");
}
buffer.position(pos);
buffer.getInt(); // signature
short thisDisk = buffer.getShort();
short cdStartDisk = buffer.getShort();
short entriesThisDisk = buffer.getShort();
short totalEntries = buffer.getShort();
int cdSize = buffer.getInt();
int cdOffset = buffer.getInt();
short commentLen = buffer.getShort();
byte[] commentBytes = new byte[commentLen];
buffer.get(commentBytes);
String comment = new String(commentBytes, "UTF-8");
properties.put("End of Central Directory Signature", "0x06054b50");
properties.put("Number of this disk", thisDisk);
properties.put("Number of the disk with the start of the central directory", cdStartDisk);
properties.put("Total number of entries in the central directory on this disk", entriesThisDisk);
properties.put("Total number of entries in the central directory", totalEntries);
properties.put("Size of the central directory", cdSize);
properties.put("Offset of start of central directory", cdOffset);
properties.put("ZIP file comment length", commentLen);
properties.put("ZIP file comment", comment);
}
public void printProperties() throws IOException {
if (properties.isEmpty()) {
readProperties();
}
properties.forEach((key, value) -> System.out.println(key + ": " + value));
}
public void writeComment(String newComment) throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filename));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int pos = data.length - 22;
while (pos >= 0) {
if (buffer.getInt(pos) == 0x06054b50) {
break;
}
pos--;
}
if (pos < 0) {
throw new IOException("Cannot write: Invalid file.");
}
short oldCommentLen = buffer.getShort(pos + 20);
byte[] newCommentBytes = newComment.getBytes("UTF-8");
byte[] newData = new byte[pos + 22 + newCommentBytes.length];
System.arraycopy(data, 0, newData, 0, pos + 22);
System.arraycopy(newCommentBytes, 0, newData, pos + 22, newCommentBytes.length);
ByteBuffer.wrap(newData).order(ByteOrder.LITTLE_ENDIAN).putShort(pos + 20, (short) newCommentBytes.length);
Files.write(Paths.get(filename), newData);
}
// Example usage:
// public static void main(String[] args) throws IOException {
// JarParser parser = new JarParser("example.jar");
// parser.printProperties();
// parser.writeComment("New comment");
// }
}
- JavaScript class for opening, decoding, reading, writing, and printing .JAR properties:
This class assumes an ArrayBuffer input (e.g., from FileReader). For writing, it updates the comment and returns a new Blob. Note: Client-side writing saves via download.
class JarParser {
constructor(buffer) {
this.buffer = buffer;
this.view = new DataView(buffer);
this.properties = {};
}
readProperties() {
let pos = this.buffer.byteLength - 22;
while (pos >= 0) {
if (this.view.getUint32(pos, true) === 0x06054b50) {
break;
}
pos--;
}
if (pos < 0) {
throw new Error('Invalid JAR/ZIP file: End of central directory signature not found.');
}
this.properties = {
'End of Central Directory Signature': '0x' + this.view.getUint32(pos, true).toString(16),
'Number of this disk': this.view.getUint16(pos + 4, true),
'Number of the disk with the start of the central directory': this.view.getUint16(pos + 6, true),
'Total number of entries in the central directory on this disk': this.view.getUint16(pos + 8, true),
'Total number of entries in the central directory': this.view.getUint16(pos + 10, true),
'Size of the central directory': this.view.getUint32(pos + 12, true),
'Offset of start of central directory': this.view.getUint32(pos + 16, true),
'ZIP file comment length': this.view.getUint16(pos + 20, true),
'ZIP file comment': new TextDecoder().decode(new Uint8Array(this.buffer, pos + 22, this.view.getUint16(pos + 20, true))),
};
}
printProperties() {
if (Object.keys(this.properties).length === 0) {
this.readProperties();
}
console.log(this.properties);
}
writeComment(newComment) {
let pos = this.buffer.byteLength - 22;
while (pos >= 0) {
if (this.view.getUint32(pos, true) === 0x06054b50) {
break;
}
pos--;
}
if (pos < 0) {
throw new Error('Cannot write: Invalid file.');
}
const oldCommentLen = this.view.getUint16(pos + 20, true);
const newCommentBytes = new TextEncoder().encode(newComment);
const newBuffer = new ArrayBuffer(pos + 22 + newCommentBytes.length);
const newView = new DataView(newBuffer);
new Uint8Array(newBuffer).set(new Uint8Array(this.buffer, 0, pos + 22));
new Uint8Array(newBuffer, pos + 22).set(newCommentBytes);
newView.setUint16(pos + 20, newCommentBytes.length, true);
return new Blob([newBuffer], { type: 'application/java-archive' });
}
}
// Example usage:
// const reader = new FileReader();
// reader.onload = () => {
// const parser = new JarParser(reader.result);
// parser.printProperties();
// const updatedBlob = parser.writeComment('New comment');
// // To save: const url = URL.createObjectURL(updatedBlob); then download
// };
// reader.readAsArrayBuffer(file);
- C class (struct with functions) for opening, decoding, reading, writing, and printing .JAR properties:
In C, we use a struct. This implementation uses fread for parsing. For writing, it updates the comment.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char* filename;
// Properties as array of strings for simplicity
char* properties[9][2]; // key-value pairs
} JarParser;
void init_jar_parser(JarParser* parser, const char* filename) {
parser->filename = strdup(filename);
for (int i = 0; i < 9; i++) {
parser->properties[i][0] = NULL;
parser->properties[i][1] = NULL;
}
}
void free_jar_parser(JarParser* parser) {
free(parser->filename);
for (int i = 0; i < 9; i++) {
free(parser->properties[i][0]);
free(parser->properties[i][1]);
}
}
int read_properties(JarParser* parser) {
FILE* f = fopen(parser->filename, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long size = ftell(f);
uint8_t* data = malloc(size);
fseek(f, 0, SEEK_SET);
fread(data, 1, size, f);
fclose(f);
long pos = size - 22;
while (pos >= 0) {
uint32_t sig;
memcpy(&sig, data + pos, 4);
if (sig == 0x06054b50) {
break;
}
pos--;
}
if (pos < 0) {
free(data);
return -1;
}
// Extract fields
uint16_t this_disk, cd_start_disk, entries_this_disk, total_entries, comment_len;
uint32_t cd_size, cd_offset;
memcpy(&this_disk, data + pos + 4, 2);
memcpy(&cd_start_disk, data + pos + 6, 2);
memcpy(&entries_this_disk, data + pos + 8, 2);
memcpy(&total_entries, data + pos + 10, 2);
memcpy(&cd_size, data + pos + 12, 4);
memcpy(&cd_offset, data + pos + 16, 4);
memcpy(&comment_len, data + pos + 20, 2);
char* comment = malloc(comment_len + 1);
memcpy(comment, data + pos + 22, comment_len);
comment[comment_len] = '\0';
// Store as strings
char buf[32];
parser->properties[0][0] = strdup("End of Central Directory Signature");
parser->properties[0][1] = strdup("0x06054b50");
parser->properties[1][0] = strdup("Number of this disk");
sprintf(buf, "%u", this_disk);
parser->properties[1][1] = strdup(buf);
parser->properties[2][0] = strdup("Number of the disk with the start of the central directory");
sprintf(buf, "%u", cd_start_disk);
parser->properties[2][1] = strdup(buf);
parser->properties[3][0] = strdup("Total number of entries in the central directory on this disk");
sprintf(buf, "%u", entries_this_disk);
parser->properties[3][1] = strdup(buf);
parser->properties[4][0] = strdup("Total number of entries in the central directory");
sprintf(buf, "%u", total_entries);
parser->properties[4][1] = strdup(buf);
parser->properties[5][0] = strdup("Size of the central directory");
sprintf(buf, "%u", cd_size);
parser->properties[5][1] = strdup(buf);
parser->properties[6][0] = strdup("Offset of start of central directory");
sprintf(buf, "%u", cd_offset);
parser->properties[6][1] = strdup(buf);
parser->properties[7][0] = strdup("ZIP file comment length");
sprintf(buf, "%u", comment_len);
parser->properties[7][1] = strdup(buf);
parser->properties[8][0] = strdup("ZIP file comment");
parser->properties[8][1] = strdup(comment);
free(comment);
free(data);
return 0;
}
void print_properties(JarParser* parser) {
if (!parser->properties[0][0]) {
if (read_properties(parser) != 0) {
printf("Error reading properties.\n");
return;
}
}
for (int i = 0; i < 9; i++) {
if (parser->properties[i][0]) {
printf("%s: %s\n", parser->properties[i][0], parser->properties[i][1]);
}
}
}
int write_comment(JarParser* parser, const char* new_comment) {
FILE* f = fopen(parser->filename, "rb+");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long size = ftell(f);
uint8_t* data = malloc(size);
fseek(f, 0, SEEK_SET);
fread(data, 1, size, f);
long pos = size - 22;
while (pos >= 0) {
uint32_t sig;
memcpy(&sig, data + pos, 4);
if (sig == 0x06054b50) {
break;
}
pos--;
}
if (pos < 0) {
free(data);
fclose(f);
return -1;
}
uint16_t old_comment_len;
memcpy(&old_comment_len, data + pos + 20, 2);
size_t new_comment_len = strlen(new_comment);
long new_size = pos + 22 + new_comment_len;
uint8_t* new_data = malloc(new_size);
memcpy(new_data, data, pos + 22);
memcpy(new_data + pos + 22, new_comment, new_comment_len);
memcpy(new_data + pos + 20, &new_comment_len, 2); // Update length
fseek(f, 0, SEEK_SET);
fwrite(new_data, 1, new_size, f);
if (new_size < size) {
ftruncate(fileno(f), new_size);
}
fclose(f);
free(data);
free(new_data);
return 0;
}
// Example usage:
// int main() {
// JarParser parser;
// init_jar_parser(&parser, "example.jar");
// print_properties(&parser);
// write_comment(&parser, "New comment");
// free_jar_parser(&parser);
// return 0;
// }