Skip to content

Commit 42b4e66

Browse files
committed
Display proper volume descriptor timestamp.
1 parent cfba006 commit 42b4e66

File tree

1 file changed

+85
-27
lines changed

1 file changed

+85
-27
lines changed

Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660VolumeDescriptor.java

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import ghidra.util.exception.DuplicateNameException;
2121

2222
import java.io.IOException;
23+
import java.time.DateTimeException;
24+
import java.time.LocalDateTime;
2325

2426
public class ISO9660VolumeDescriptor extends ISO9660BaseVolume {
2527
private byte unused; // Always 0x00
@@ -192,7 +194,7 @@ else if (super.getTypeCode() == ISO9660Constants.VOLUME_DESC_SUPPL_VOLUME_DESC)
192194

193195
@Override
194196
public String toString() {
195-
StringBuffer buff = new StringBuffer();
197+
StringBuilder buff = new StringBuilder();
196198

197199
buff.append("Type Code: 0x" + Integer.toHexString(super.getTypeCode()) + " => " +
198200
getTypeCodeString() + "\n");
@@ -246,38 +248,94 @@ public String toString() {
246248
}
247249

248250
/**
249-
* Creates a formatted date/time string based on the byteArray passed in.
250-
* @param byteArray the array containing the bytes to format into date/time
251-
* @return The formatted date/time string
251+
* Checks whether the given string is entirely made up of ASCII digits.
252+
*
253+
* @param string the string to check.
254+
* @return true if all characters in the string are ASCII digits, false
255+
* otherwise.
252256
*/
257+
private boolean isDigitsStringValid(String string) {
258+
for (int i = 0; i < string.length(); i++) {
259+
char c = string.charAt(i);
260+
if (c < '0' || c > '9') {
261+
return false;
262+
}
263+
}
264+
265+
return true;
266+
}
253267

268+
/**
269+
* Parses the given buffer as an ISO9660 timestamp and returns it as a
270+
* human readable string representation.
271+
*
272+
* Invalid buffers that are still big enough to hold a timestamp are
273+
* still parsed and converted, albeit they are marked as invalid when
274+
* presented to the user.
275+
*
276+
* @param byteArray the buffer to parse (only extended timestamp format
277+
* is handled).
278+
* @return a string with the human readable timestamp.
279+
*/
254280
protected String createDateTimeString(byte[] byteArray) {
281+
if (byteArray == null || byteArray.length < 17) {
282+
return "INVALID (truncated or missing)";
283+
}
284+
285+
String s1, s2, s3, s4, s5, s6, s7;
286+
287+
// Time zone offset from GMT in 15 minute intervals,
288+
// starting at interval -48 (west) and running up to
289+
// interval 52 (east)
290+
int timeOffset = byteArray[byteArray.length - 1];
291+
292+
String bString = new String(byteArray);
293+
s1 = bString.substring(0, 4); //year 1 to 9999
294+
s2 = bString.substring(4, 6); //month 1 to 12
295+
s3 = bString.substring(6, 8); //day 1 to 31
296+
s4 = bString.substring(8, 10); //hour 0 to 23
297+
s5 = bString.substring(10, 12); //minute 0 to 59
298+
s6 = bString.substring(12, 14); //second 0 to 59
299+
s7 = bString.substring(14, 16); //ms 0 to 99
300+
301+
// Validate strings first.
302+
boolean validBuffer = isDigitsStringValid(s1) && isDigitsStringValid(s2) && isDigitsStringValid(s3) &&
303+
isDigitsStringValid(s4) && isDigitsStringValid(s5) && isDigitsStringValid(s6) && isDigitsStringValid(s7);
304+
305+
try {
306+
// The buffer contains an invalid date/time.
307+
LocalDateTime.of(Integer.parseInt(s1), Integer.parseInt(s2), Integer.parseInt(s3),
308+
Integer.parseInt(s4), Integer.parseInt(s5), Integer.parseInt(s6));
309+
} catch (NumberFormatException | DateTimeException e) {
310+
validBuffer = false;
311+
}
312+
313+
// The buffer contains an invalid timezone offset.
314+
if (timeOffset < -48 || timeOffset > 52) {
315+
validBuffer = false;
316+
}
317+
318+
/*
319+
* Time zone offset from GMT in 15 minute intervals,
320+
* starting at interval -48 (west) and running up to
321+
* interval 52 (east).
322+
*/
323+
int timezoneIntegral = timeOffset / 4;
324+
int timezoneFractional = (Math.abs(timeOffset) % 4) * 15;
325+
326+
StringBuilder builder = new StringBuilder();
327+
if (!validBuffer) {
328+
builder.append("INVALID(");
329+
}
330+
331+
builder.append(String.format("%s-%s-%s %s:%s:%s.%s GMT%c%02d%02d", s1, s2, s3, s4, s5, s6, s7,
332+
timezoneIntegral < 0 ? '-' : '+', timezoneIntegral, timezoneFractional));
255333

256-
if (byteArray != null) {
257-
String s1, s2, s3, s4, s5, s6, s7;
258-
int timeOffset = byteArray[byteArray.length - 1];
259-
String bString = new String(byteArray);
260-
s1 = bString.substring(0, 4); //year 1 to 9999
261-
s2 = bString.substring(4, 6); //month 1 to 12
262-
s3 = bString.substring(6, 8); //day 1 to 31
263-
s4 = bString.substring(8, 10); //hour 0 to 23
264-
s5 = bString.substring(10, 12); //minute 0 to 59
265-
s6 = bString.substring(12, 14); //second 0 to 59
266-
s7 = bString.substring(14, 16); //ms 0 to 99
267-
268-
/*
269-
* Time zone offset from GMT in 15 minute intervals,
270-
* starting at interval -48 (west) and running up to
271-
* interval 52 (east).
272-
*/
273-
int timeZone = (-48 + timeOffset) / 4; //GMT offset
274-
String dateTime =
275-
String.format("%s-%s-%s %s:%s:%s.%s GMT%d", s1, s2, s3, s4, s5, s6, s7, timeZone);
276-
277-
return dateTime;
334+
if (!validBuffer) {
335+
builder.append(")");
278336
}
279-
return "";
280337

338+
return builder.toString();
281339
}
282340

283341
public byte getUnused() {

0 commit comments

Comments
 (0)