|
20 | 20 | import ghidra.util.exception.DuplicateNameException;
|
21 | 21 |
|
22 | 22 | import java.io.IOException;
|
| 23 | +import java.time.DateTimeException; |
| 24 | +import java.time.LocalDateTime; |
23 | 25 |
|
24 | 26 | public class ISO9660VolumeDescriptor extends ISO9660BaseVolume {
|
25 | 27 | private byte unused; // Always 0x00
|
@@ -192,7 +194,7 @@ else if (super.getTypeCode() == ISO9660Constants.VOLUME_DESC_SUPPL_VOLUME_DESC)
|
192 | 194 |
|
193 | 195 | @Override
|
194 | 196 | public String toString() {
|
195 |
| - StringBuffer buff = new StringBuffer(); |
| 197 | + StringBuilder buff = new StringBuilder(); |
196 | 198 |
|
197 | 199 | buff.append("Type Code: 0x" + Integer.toHexString(super.getTypeCode()) + " => " +
|
198 | 200 | getTypeCodeString() + "\n");
|
@@ -246,38 +248,94 @@ public String toString() {
|
246 | 248 | }
|
247 | 249 |
|
248 | 250 | /**
|
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. |
252 | 256 | */
|
| 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 | + } |
253 | 267 |
|
| 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 | + */ |
254 | 280 | 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)); |
255 | 333 |
|
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(")"); |
278 | 336 | }
|
279 |
| - return ""; |
280 | 337 |
|
| 338 | + return builder.toString(); |
281 | 339 | }
|
282 | 340 |
|
283 | 341 | public byte getUnused() {
|
|
0 commit comments