    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     */
    019    package org.apache.commons.compress.archivers.ar;
    020
    021    import java.io.File;
    022    import java.io.IOException;
    023    import java.io.OutputStream;
    024
    025    import org.apache.commons.compress.archivers.ArchiveEntry;
    026    import org.apache.commons.compress.archivers.ArchiveOutputStream;
    027    import org.apache.commons.compress.utils.ArchiveUtils;
    028
    029    /**
    030     * Implements the "ar" archive format as an output stream.
    031     *
    032     * @NotThreadSafe
    033     */
    034    public class ArArchiveOutputStream extends ArchiveOutputStream {
    035
    036        private final OutputStream out;
    037        private long archiveOffset = 0;
    038        private long entryOffset = 0;
    039        private ArArchiveEntry prevEntry;
    040        private boolean haveUnclosedEntry = false;
    041
    042        /** indicates if this archive is finished */
    043        private boolean finished = false;
    044
    045        public ArArchiveOutputStream( final OutputStream pOut ) {
    046            this.out = pOut;
    047        }
    048
    049        private long writeArchiveHeader() throws IOException {
    050            byte [] header = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER);
    051            out.write(header);
    052            return header.length;
    053        }
    054
    055        /** {@inheritDoc} */
    056        public void closeArchiveEntry() throws IOException {
    057            if(finished) {
    058                throw new IOException("Stream has already been finished");
    059            }
    060            if (prevEntry == null || !haveUnclosedEntry){
    061                throw new IOException("No current entry to close");
    062            }
    063            if ((entryOffset % 2) != 0) {
    064                out.write('\n'); // Pad byte
    065                archiveOffset++;
    066            }
    067            haveUnclosedEntry = false;
    068        }
    069
    070        /** {@inheritDoc} */
    071        public void putArchiveEntry( final ArchiveEntry pEntry ) throws IOException {
    072            if(finished) {
    073                throw new IOException("Stream has already been finished");
    074            }
    075
    076            ArArchiveEntry pArEntry = (ArArchiveEntry)pEntry;
    077            if (prevEntry == null) {
    078                archiveOffset += writeArchiveHeader();
    079            } else {
    080                if (prevEntry.getLength() != entryOffset) {
    081                    throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
    082                }
    083
    084                if (haveUnclosedEntry) {
    085                    closeArchiveEntry();
    086                }
    087            }
    088
    089            prevEntry = pArEntry;
    090
    091            archiveOffset += writeEntryHeader(pArEntry);
    092
    093            entryOffset = 0;
    094            haveUnclosedEntry = true;
    095        }
    096
    097        private long fill( final long pOffset, final long pNewOffset, final char pFill ) throws IOException {
    098            final long diff = pNewOffset - pOffset;
    099
    100            if (diff > 0) {
    101                for (int i = 0; i < diff; i++) {
    102                    write(pFill);
    103                }
    104            }
    105
    106            return pNewOffset;
    107        }
    108
    109        private long write( final String data ) throws IOException {
    110            final byte[] bytes = data.getBytes("ascii");
    111            write(bytes);
    112            return bytes.length;
    113        }
    114
    115        private long writeEntryHeader( final ArArchiveEntry pEntry ) throws IOException {
    116
    117            long offset = 0;
    118
    119            final String n = pEntry.getName();
    120            if (n.length() > 16) {
    121                throw new IOException("filename too long, > 16 chars: "+n);
    122            }
    123            offset += write(n);
    124
    125            offset = fill(offset, 16, ' ');
    126            final String m = "" + (pEntry.getLastModified());
    127            if (m.length() > 12) {
    128                throw new IOException("modified too long");
    129            }
    130            offset += write(m);
    131
    132            offset = fill(offset, 28, ' ');
    133            final String u = "" + pEntry.getUserId();
    134            if (u.length() > 6) {
    135                throw new IOException("userid too long");
    136            }
    137            offset += write(u);
    138
    139            offset = fill(offset, 34, ' ');
    140            final String g = "" + pEntry.getGroupId();
    141            if (g.length() > 6) {
    142                throw new IOException("groupid too long");
    143            }
    144            offset += write(g);
    145
    146            offset = fill(offset, 40, ' ');
    147            final String fm = "" + Integer.toString(pEntry.getMode(), 8);
    148            if (fm.length() > 8) {
    149                throw new IOException("filemode too long");
    150            }
    151            offset += write(fm);
    152
    153            offset = fill(offset, 48, ' ');
    154            final String s = "" + pEntry.getLength();
    155            if (s.length() > 10) {
    156                throw new IOException("size too long");
    157            }
    158            offset += write(s);
    159
    160            offset = fill(offset, 58, ' ');
    161
    162            offset += write(ArArchiveEntry.TRAILER);
    163
    164            return offset;
    165        }
    166
    167        public void write(byte[] b, int off, int len) throws IOException {
    168            out.write(b, off, len);
    169            count(len);
    170            entryOffset += len;
    171        }
    172
    173        /**
    174         * Calls finish if necessary, and then closes the OutputStream
    175         */
    176        public void close() throws IOException {
    177            if(!finished) {
    178                finish();
    179            }
    180            out.close();
    181            prevEntry = null;
    182        }
    183
    184        /** {@inheritDoc} */
    185        public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
    186                throws IOException {
    187            if(finished) {
    188                throw new IOException("Stream has already been finished");
    189            }
    190            return new ArArchiveEntry(inputFile, entryName);
    191        }
    192
    193        /** {@inheritDoc} */
    194        public void finish() throws IOException {
    195            if(haveUnclosedEntry) {
    196                throw new IOException("This archive contains unclosed entries.");
    197            } else if(finished) {
    198                throw new IOException("This archive has already been finished");
    199            }
    200            finished = true;
    201        }
    202    }