001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.archivers.zip;
020
021import java.io.Serializable;
022import java.nio.file.attribute.FileTime;
023import java.util.Arrays;
024import java.util.Date;
025import java.util.Objects;
026import java.util.zip.ZipException;
027
028import org.apache.commons.compress.utils.TimeUtils;
029
030/**
031 * <p>An extra field that stores additional file and directory timestamp data
032 * for ZIP entries.   Each ZIP entry can include up to three timestamps
033 * (modify, access, create*).  The timestamps are stored as 32 bit signed
034 * integers representing seconds since UNIX epoch (Jan 1st, 1970, UTC).
035 * This field improves on ZIP's default timestamp granularity, since it
036 * allows one to store additional timestamps, and, in addition, the timestamps
037 * are stored using per-second granularity (zip's default behavior can only store
038 * timestamps to the nearest <em>even</em> second).
039 * </p><p>
040 * Unfortunately, 32 (signed) bits can only store dates up to the year 2037,
041 * and so this extra field will eventually be obsolete.  Enjoy it while it lasts!
042 * </p>
043 * <ul>
044 * <li><b>modifyTime:</b>
045 * most recent time of file/directory modification
046 * (or file/dir creation if the entry has not been
047 * modified since it was created).
048 * </li>
049 * <li><b>accessTime:</b>
050 * most recent time file/directory was opened
051 * (e.g., read from disk).  Many people disable
052 * their operating systems from updating this value
053 * using the NOATIME mount option to optimize disk behavior,
054 * and thus it's not always reliable.  In those cases
055 * it's always equal to modifyTime.
056 * </li>
057 * <li><b>*createTime:</b>
058 * modern Linux file systems (e.g., ext2 and newer)
059 * do not appear to store a value like this, and so
060 * it's usually omitted altogether in the ZIP extra
061 * field.  Perhaps other Unix systems track this.
062 * </li></ul>
063 * <p>
064 * We're using the field definition given in Info-Zip's source archive:
065 * zip-3.0.tar.gz/proginfo/extrafld.txt
066 * </p>
067 * <pre>
068 * Value         Size        Description
069 * -----         ----        -----------
070 * 0x5455        Short       tag for this extra block type ("UT")
071 * TSize         Short       total data size for this block
072 * Flags         Byte        info bits
073 * (ModTime)     Long        time of last modification (UTC/GMT)
074 * (AcTime)      Long        time of last access (UTC/GMT)
075 * (CrTime)      Long        time of original creation (UTC/GMT)
076 *
077 * Central-header version:
078 *
079 * Value         Size        Description
080 * -----         ----        -----------
081 * 0x5455        Short       tag for this extra block type ("UT")
082 * TSize         Short       total data size for this block
083 * Flags         Byte        info bits (refers to local header!)
084 * (ModTime)     Long        time of last modification (UTC/GMT)
085 * </pre>
086 * @since 1.5
087 */
088public class X5455_ExtendedTimestamp implements ZipExtraField, Cloneable, Serializable {
089    private static final long serialVersionUID = 1L;
090
091    /**
092     * The header ID for this extra field.
093     *
094     * @since 1.23
095     */
096    public static final ZipShort HEADER_ID = new ZipShort(0x5455);
097
098    /**
099     * The bit set inside the flags by when the last modification time
100     * is present in this extra field.
101     */
102    public static final byte MODIFY_TIME_BIT = 1;
103    /**
104     * The bit set inside the flags by when the lasr access time is
105     * present in this extra field.
106     */
107    public static final byte ACCESS_TIME_BIT = 2;
108    /**
109     * The bit set inside the flags by when the original creation time
110     * is present in this extra field.
111     */
112    public static final byte CREATE_TIME_BIT = 4;
113
114    /**
115     * Utility method converts java.util.Date (milliseconds since epoch)
116     * into a ZipLong (seconds since epoch).
117     * <p/>
118     * Also makes sure the converted ZipLong is not too big to fit
119     * in 32 unsigned bits.
120     *
121     * @param d java.util.Date to convert to ZipLong
122     * @return ZipLong
123     */
124    private static ZipLong dateToZipLong(final Date d) {
125        if (d == null) {
126            return null;
127        }
128        return unixTimeToZipLong(d.getTime() / 1000);
129    }
130
131    /**
132     * Utility method converts {@link FileTime} into a ZipLong (seconds since epoch).
133     * <p/>
134     * Also makes sure the converted ZipLong is not too big to fit
135     * in 32 unsigned bits.
136     *
137     * @param time {@link FileTime} to convert to ZipLong
138     * @return ZipLong
139     */
140    private static ZipLong fileTimeToZipLong(final FileTime time) {
141        return time == null ? null : unixTimeToZipLong(TimeUtils.toUnixTime(time));
142    }
143
144    private static FileTime unixTimeToFileTime(final ZipLong unixTime) {
145        return unixTime != null ? TimeUtils.unixTimeToFileTime(unixTime.getIntValue()) : null;
146    }
147    // The 3 boolean fields (below) come from this flag's byte.  The remaining 5 bits
148    // are ignored according to the current version of the spec (December 2012).
149
150    private static ZipLong unixTimeToZipLong(final long unixTime) {
151        if (!TimeUtils.isUnixTime(unixTime)) {
152            throw new IllegalArgumentException("X5455 timestamps must fit in a signed 32 bit integer: " + unixTime);
153        }
154        return new ZipLong(unixTime);
155    }
156
157    private static Date zipLongToDate(final ZipLong unixTime) {
158        return unixTime != null ? new Date(unixTime.getIntValue() * 1000L) : null;
159    }
160
161    private byte flags;
162    // Note: even if bit1 and bit2 are set, the Central data will still not contain
163    // access/create fields:  only local data ever holds those!  This causes
164    // some of our implementation to look a little odd, with seemingly spurious
165    // != null and length checks.
166    private boolean bit0_modifyTimePresent;
167    private boolean bit1_accessTimePresent;
168
169    private boolean bit2_createTimePresent;
170
171    private ZipLong modifyTime;
172
173    private ZipLong accessTime;
174
175    private ZipLong createTime;
176
177    /**
178     * Constructor for X5455_ExtendedTimestamp.
179     */
180    public X5455_ExtendedTimestamp() {}
181
182    @Override
183    public Object clone() throws CloneNotSupportedException {
184        return super.clone();
185    }
186
187    @Override
188    public boolean equals(final Object o) {
189        if (o instanceof X5455_ExtendedTimestamp) {
190            final X5455_ExtendedTimestamp xf = (X5455_ExtendedTimestamp) o;
191
192            // The ZipLong==ZipLong clauses handle the cases where both are null.
193            // and only last 3 bits of flags matter.
194            return ((flags & 0x07) == (xf.flags & 0x07)) &&
195                    Objects.equals(modifyTime, xf.modifyTime) &&
196                    Objects.equals(accessTime, xf.accessTime) &&
197                    Objects.equals(createTime, xf.createTime);
198        }
199        return false;
200    }
201
202    /**
203     * Gets the access time as a {@link FileTime}
204     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
205     * The milliseconds are always zeroed out, since the underlying data
206     * offers only per-second precision.
207     *
208     * @return modify time as {@link FileTime} or null.
209     * @since 1.23
210     */
211    public FileTime getAccessFileTime() {
212        return unixTimeToFileTime(accessTime);
213    }
214
215    /**
216     * Gets the access time as a java.util.Date
217     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
218     * The milliseconds are always zeroed out, since the underlying data
219     * offers only per-second precision.
220     *
221     * @return access time as java.util.Date or null.
222     */
223    public Date getAccessJavaTime() {
224        return zipLongToDate(accessTime);
225    }
226
227    /**
228     * Gets the access time (seconds since epoch) of this ZIP entry
229     * as a ZipLong object, or null if no such timestamp exists in the
230     * ZIP entry.
231     *
232     * @return access time (seconds since epoch) or null.
233     */
234    public ZipLong getAccessTime() {
235        return accessTime;
236    }
237
238    /**
239     * Gets the actual data to put into central directory data - without Header-ID
240     * or length specifier.
241     *
242     * @return the central directory data
243     */
244    @Override
245    public byte[] getCentralDirectoryData() {
246        // Truncate out create & access time (last 8 bytes) from
247        // the copy of the local data we obtained:
248        return Arrays.copyOf(getLocalFileDataData(), getCentralDirectoryLength().getValue());
249    }
250
251    /**
252     * Gets the length of the extra field in the local file data - without
253     * Header-ID or length specifier.
254     *
255     * <p>For X5455 the central length is often smaller than the
256     * local length, because central cannot contain access or create
257     * timestamps.</p>
258     *
259     * @return a {@code ZipShort} for the length of the data of this extra field
260     */
261    @Override
262    public ZipShort getCentralDirectoryLength() {
263        return new ZipShort(1 + (bit0_modifyTimePresent ? 4 : 0));
264    }
265
266    /**
267     * Gets the create time as a {@link FileTime}
268     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
269     * The milliseconds are always zeroed out, since the underlying data
270     * offers only per-second precision.
271     *
272     * @return modify time as {@link FileTime} or null.
273     * @since 1.23
274     */
275    public FileTime getCreateFileTime() {
276        return unixTimeToFileTime(createTime);
277    }
278
279    /**
280     * <p>
281     * Gets the create time as a java.util.Date
282     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
283     * The milliseconds are always zeroed out, since the underlying data
284     * offers only per-second precision.
285     * </p>
286     * <p>
287     * Note: modern Linux file systems (e.g., ext2)
288     * do not appear to store a "create time" value, and so
289     * it's usually omitted altogether in the ZIP extra
290     * field. Perhaps other Unix systems track this.
291     * </p>
292     *
293     * @return create time as java.util.Date or null.
294     */
295    public Date getCreateJavaTime() {
296        return zipLongToDate(createTime);
297    }
298
299    /**
300     * <p>
301     * Gets the create time (seconds since epoch) of this ZIP entry
302     * as a ZipLong object, or null if no such timestamp exists in the
303     * ZIP entry.
304     * </p>
305     * <p>
306     * Note: modern Linux file systems (e.g., ext2)
307     * do not appear to store a "create time" value, and so
308     * it's usually omitted altogether in the ZIP extra
309     * field. Perhaps other Unix systems track this.
310     * </p>
311     *
312     * @return create time (seconds since epoch) or null.
313     */
314    public ZipLong getCreateTime() {
315        return createTime;
316    }
317
318    /**
319     * Gets flags byte.  The flags byte tells us which of the
320     * three datestamp fields are present in the data:
321     * <pre>
322     * bit0 - modify time
323     * bit1 - access time
324     * bit2 - create time
325     * </pre>
326     * Only first 3 bits of flags are used according to the
327     * latest version of the spec (December 2012).
328     *
329     * @return flags byte indicating which of the
330     *         three datestamp fields are present.
331     */
332    public byte getFlags() { return flags; }
333
334    /**
335     * Gets the Header-ID.
336     *
337     * @return the value for the header id for this extrafield
338     */
339    @Override
340    public ZipShort getHeaderId() {
341        return HEADER_ID;
342    }
343
344    /**
345     * Gets the actual data to put into local file data - without Header-ID
346     * or length specifier.
347     *
348     * @return get the data
349     */
350    @Override
351    public byte[] getLocalFileDataData() {
352        final byte[] data = new byte[getLocalFileDataLength().getValue()];
353        int pos = 0;
354        data[pos++] = 0;
355        if (bit0_modifyTimePresent) {
356            data[0] |= MODIFY_TIME_BIT;
357            System.arraycopy(modifyTime.getBytes(), 0, data, pos, 4);
358            pos += 4;
359        }
360        if (bit1_accessTimePresent && accessTime != null) {
361            data[0] |= ACCESS_TIME_BIT;
362            System.arraycopy(accessTime.getBytes(), 0, data, pos, 4);
363            pos += 4;
364        }
365        if (bit2_createTimePresent && createTime != null) {
366            data[0] |= CREATE_TIME_BIT;
367            System.arraycopy(createTime.getBytes(), 0, data, pos, 4);
368            pos += 4; // NOSONAR - assignment as documentation
369        }
370        return data;
371    }
372
373    /**
374     * Gets the length of the extra field in the local file data - without
375     * Header-ID or length specifier.
376     *
377     * @return a {@code ZipShort} for the length of the data of this extra field
378     */
379    @Override
380    public ZipShort getLocalFileDataLength() {
381        return new ZipShort(1 +
382                (bit0_modifyTimePresent ? 4 : 0) +
383                (bit1_accessTimePresent && accessTime != null ? 4 : 0) +
384                (bit2_createTimePresent && createTime != null ? 4 : 0)
385        );
386    }
387
388    /**
389     * Gets the modify time as a {@link FileTime}
390     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
391     * The milliseconds are always zeroed out, since the underlying data
392     * offers only per-second precision.
393     *
394     * @return modify time as {@link FileTime} or null.
395     * @since 1.23
396     */
397    public FileTime getModifyFileTime() {
398        return unixTimeToFileTime(modifyTime);
399    }
400
401    /**
402     * Gets the modify time as a java.util.Date
403     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
404     * The milliseconds are always zeroed out, since the underlying data
405     * offers only per-second precision.
406     *
407     * @return modify time as java.util.Date or null.
408     */
409    public Date getModifyJavaTime() {
410        return zipLongToDate(modifyTime);
411    }
412
413    /**
414     * Gets the modify time (seconds since epoch) of this ZIP entry
415     * as a ZipLong object, or null if no such timestamp exists in the
416     * ZIP entry.
417     *
418     * @return modify time (seconds since epoch) or null.
419     */
420    public ZipLong getModifyTime() { return modifyTime; }
421
422    @Override
423    public int hashCode() {
424        int hc = (-123 * (flags & 0x07)); // only last 3 bits of flags matter
425        if (modifyTime != null) {
426            hc ^= modifyTime.hashCode();
427        }
428        if (accessTime != null) {
429            // Since accessTime is often same as modifyTime,
430            // this prevents them from XOR negating each other.
431            hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
432        }
433        if (createTime != null) {
434            hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
435        }
436        return hc;
437    }
438
439    /**
440     * Tests whether bit0 of the flags byte is set or not,
441     * which should correspond to the presence or absence of
442     * a modify timestamp in this particular ZIP entry.
443     *
444     * @return true if bit0 of the flags byte is set.
445     */
446    public boolean isBit0_modifyTimePresent() {
447        return bit0_modifyTimePresent;
448    }
449
450    /**
451     * Tests whether bit1 of the flags byte is set or not,
452     * which should correspond to the presence or absence of
453     * a "last access" timestamp in this particular ZIP entry.
454     *
455     * @return true if bit1 of the flags byte is set.
456     */
457    public boolean isBit1_accessTimePresent() {
458        return bit1_accessTimePresent;
459    }
460
461    /**
462     * Tests whether bit2 of the flags byte is set or not,
463     * which should correspond to the presence or absence of
464     * a create timestamp in this particular ZIP entry.
465     *
466     * @return true if bit2 of the flags byte is set.
467     */
468    public boolean isBit2_createTimePresent() {
469        return bit2_createTimePresent;
470    }
471
472    /**
473     * Doesn't do anything special since this class always uses the
474     * same parsing logic for both central directory and local file data.
475     */
476    @Override
477    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
478        reset();
479        parseFromLocalFileData(buffer, offset, length);
480    }
481
482    /**
483     * Populate data from this array as if it was in local file data.
484     *
485     * @param data   an array of bytes
486     * @param offset the start offset
487     * @param length the number of bytes in the array from offset
488     * @throws java.util.zip.ZipException on error
489     */
490    @Override
491    public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
492        reset();
493        if (length < 1) {
494            throw new ZipException("X5455_ExtendedTimestamp too short, only " + length + " bytes");
495        }
496        final int len = offset + length;
497        setFlags(data[offset++]);
498        if (bit0_modifyTimePresent && offset + 4 <= len) {
499            modifyTime = new ZipLong(data, offset);
500            offset += 4;
501        } else {
502            bit0_modifyTimePresent = false;
503        }
504        if (bit1_accessTimePresent && offset + 4 <= len) {
505            accessTime = new ZipLong(data, offset);
506            offset += 4;
507        } else {
508            bit1_accessTimePresent = false;
509        }
510        if (bit2_createTimePresent && offset + 4 <= len) {
511            createTime = new ZipLong(data, offset);
512            offset += 4; // NOSONAR - assignment as documentation
513        } else {
514            bit2_createTimePresent = false;
515        }
516    }
517
518    /**
519     * Reset state back to newly constructed state.  Helps us make sure
520     * parse() calls always generate clean results.
521     */
522    private void reset() {
523        setFlags((byte) 0);
524        this.modifyTime = null;
525        this.accessTime = null;
526        this.createTime = null;
527    }
528
529    /**
530     * <p>
531     * Sets the acccess time as a {@link FileTime}
532     * of this ZIP entry. Supplied value is truncated to per-second
533     * precision (milliseconds zeroed-out).
534     * </p><p>
535     * Note: the setters for flags and timestamps are decoupled.
536     * Even if the timestamp is not-null, it will only be written
537     * out if the corresponding bit in the flags is also set.
538     * </p>
539     *
540     * @param time access time as {@link FileTime}
541     * @since 1.23
542     */
543    public void setAccessFileTime(final FileTime time) {
544        setAccessTime(fileTimeToZipLong(time));
545    }
546
547    /**
548     * <p>
549     * Sets the access time as a java.util.Date
550     * of this ZIP entry.  Supplied value is truncated to per-second
551     * precision (milliseconds zeroed-out).
552     * </p><p>
553     * Note: the setters for flags and timestamps are decoupled.
554     * Even if the timestamp is not-null, it will only be written
555     * out if the corresponding bit in the flags is also set.
556     * </p>
557     *
558     * @param d access time as java.util.Date
559     */
560    public void setAccessJavaTime(final Date d) {
561        setAccessTime(dateToZipLong(d));
562    }
563
564    /**
565     * <p>
566     * Sets the access time (seconds since epoch) of this ZIP entry
567     * using a ZipLong object
568     * </p><p>
569     * Note: the setters for flags and timestamps are decoupled.
570     * Even if the timestamp is not-null, it will only be written
571     * out if the corresponding bit in the flags is also set.
572     * </p>
573     *
574     * @param l ZipLong of the access time (seconds per epoch)
575     */
576    public void setAccessTime(final ZipLong l) {
577        bit1_accessTimePresent = l != null;
578        flags = (byte) (l != null ? (flags | ACCESS_TIME_BIT) : (flags & ~ACCESS_TIME_BIT));
579        this.accessTime = l;
580    }
581
582    /**
583     * <p>
584     * Sets the create time as a {@link FileTime}
585     * of this ZIP entry. Supplied value is truncated to per-second
586     * precision (milliseconds zeroed-out).
587     * </p><p>
588     * Note: the setters for flags and timestamps are decoupled.
589     * Even if the timestamp is not-null, it will only be written
590     * out if the corresponding bit in the flags is also set.
591     * </p>
592     *
593     * @param time create time as {@link FileTime}
594     * @since 1.23
595     */
596    public void setCreateFileTime(final FileTime time) {
597        setCreateTime(fileTimeToZipLong(time));
598    }
599
600    /**
601     * <p>
602     * Sets the create time as a java.util.Date
603     * of this ZIP entry.  Supplied value is truncated to per-second
604     * precision (milliseconds zeroed-out).
605     * </p><p>
606     * Note: the setters for flags and timestamps are decoupled.
607     * Even if the timestamp is not-null, it will only be written
608     * out if the corresponding bit in the flags is also set.
609     * </p>
610     *
611     * @param d create time as java.util.Date
612     */
613    public void setCreateJavaTime(final Date d) { setCreateTime(dateToZipLong(d)); }
614
615    /**
616     * <p>
617     * Sets the create time (seconds since epoch) of this ZIP entry
618     * using a ZipLong object
619     * </p><p>
620     * Note: the setters for flags and timestamps are decoupled.
621     * Even if the timestamp is not-null, it will only be written
622     * out if the corresponding bit in the flags is also set.
623     * </p>
624     *
625     * @param l ZipLong of the create time (seconds per epoch)
626     */
627    public void setCreateTime(final ZipLong l) {
628        bit2_createTimePresent = l != null;
629        flags = (byte) (l != null ? (flags | CREATE_TIME_BIT) : (flags & ~CREATE_TIME_BIT));
630        this.createTime = l;
631    }
632
633    /**
634     * Sets flags byte.  The flags byte tells us which of the
635     * three datestamp fields are present in the data:
636     * <pre>
637     * bit0 - modify time
638     * bit1 - access time
639     * bit2 - create time
640     * </pre>
641     * Only first 3 bits of flags are used according to the
642     * latest version of the spec (December 2012).
643     *
644     * @param flags flags byte indicating which of the
645     *              three datestamp fields are present.
646     */
647    public void setFlags(final byte flags) {
648        this.flags = flags;
649        this.bit0_modifyTimePresent = (flags & MODIFY_TIME_BIT) == MODIFY_TIME_BIT;
650        this.bit1_accessTimePresent = (flags & ACCESS_TIME_BIT) == ACCESS_TIME_BIT;
651        this.bit2_createTimePresent = (flags & CREATE_TIME_BIT) == CREATE_TIME_BIT;
652    }
653
654    /**
655     * <p>
656     * Sets the modify time as a {@link FileTime}
657     * of this ZIP entry. Supplied value is truncated to per-second
658     * precision (milliseconds zeroed-out).
659     * </p><p>
660     * Note: the setters for flags and timestamps are decoupled.
661     * Even if the timestamp is not-null, it will only be written
662     * out if the corresponding bit in the flags is also set.
663     * </p>
664     *
665     * @param time modify time as {@link FileTime}
666     * @since 1.23
667     */
668    public void setModifyFileTime(final FileTime time) {
669        setModifyTime(fileTimeToZipLong(time));
670    }
671
672    /**
673     * <p>
674     * Sets the modify time as a java.util.Date
675     * of this ZIP entry.  Supplied value is truncated to per-second
676     * precision (milliseconds zeroed-out).
677     * </p><p>
678     * Note: the setters for flags and timestamps are decoupled.
679     * Even if the timestamp is not-null, it will only be written
680     * out if the corresponding bit in the flags is also set.
681     * </p>
682     *
683     * @param d modify time as java.util.Date
684     */
685    public void setModifyJavaTime(final Date d) {
686        setModifyTime(dateToZipLong(d));
687    }
688
689    /**
690     * <p>
691     * Sets the modify time (seconds since epoch) of this ZIP entry
692     * using a ZipLong object.
693     * </p><p>
694     * Note: the setters for flags and timestamps are decoupled.
695     * Even if the timestamp is not-null, it will only be written
696     * out if the corresponding bit in the flags is also set.
697     * </p>
698     *
699     * @param l ZipLong of the modify time (seconds per epoch)
700     */
701    public void setModifyTime(final ZipLong l) {
702        bit0_modifyTimePresent = l != null;
703        flags = (byte) (l != null ? (flags | MODIFY_TIME_BIT) : (flags & ~MODIFY_TIME_BIT));
704        this.modifyTime = l;
705    }
706
707    /**
708     * Returns a String representation of this class useful for
709     * debugging purposes.
710     *
711     * @return A String representation of this class useful for
712     *         debugging purposes.
713     */
714    @Override
715    public String toString() {
716        final StringBuilder buf = new StringBuilder();
717        buf.append("0x5455 Zip Extra Field: Flags=");
718        buf.append(Integer.toBinaryString(ZipUtil.unsignedIntToSignedByte(flags))).append(" ");
719        if (bit0_modifyTimePresent && modifyTime != null) {
720            final Date m = getModifyJavaTime();
721            buf.append(" Modify:[").append(m).append("] ");
722        }
723        if (bit1_accessTimePresent && accessTime != null) {
724            final Date a = getAccessJavaTime();
725            buf.append(" Access:[").append(a).append("] ");
726        }
727        if (bit2_createTimePresent && createTime != null) {
728            final Date c = getCreateJavaTime();
729            buf.append(" Create:[").append(c).append("] ");
730        }
731        return buf.toString();
732    }
733
734}