    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        public void closeArchiveEntry() throws IOException {
    056            if(finished) {
    057                throw new IOException("Stream has already been finished");
    058            }
    059            if (prevEntry == null || !haveUnclosedEntry){
    060                throw new IOException("No current entry to close");
    061            }
    062            if ((entryOffset % 2) != 0) {
    063                out.write('\n'); // Pad byte
    064                archiveOffset++;
    065            }
    066            haveUnclosedEntry = false;
    067        }
    068
    069        public void putArchiveEntry( final ArchiveEntry pEntry ) throws IOException {
    070            if(finished) {
    071                throw new IOException("Stream has already been finished");
    072            }
    073
    074            ArArchiveEntry pArEntry = (ArArchiveEntry)pEntry;
    075            if (prevEntry == null) {
    076                archiveOffset += writeArchiveHeader();
    077            } else {
    078                if (prevEntry.getLength() != entryOffset) {
    079                    throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
    080                }
    081
    082                if (haveUnclosedEntry) {
    083                    closeArchiveEntry();
    084                }
    085            }
    086
    087            prevEntry = pArEntry;
    088
    089            archiveOffset += writeEntryHeader(pArEntry);
    090
    091            entryOffset = 0;
    092            haveUnclosedEntry = true;
    093        }
    094
    095        private long fill( final long pOffset, final long pNewOffset, final char pFill ) throws IOException {
    096            final long diff = pNewOffset - pOffset;
    097
    098            if (diff > 0) {
    099                for (int i = 0; i < diff; i++) {
    100                    write(pFill);
    101                }
    102            }
    103
    104            return pNewOffset;
    105        }
    106
    107        private long write( final String data ) throws IOException {
    108            final byte[] bytes = data.getBytes("ascii");
    109            write(bytes);
    110            return bytes.length;
    111        }
    112
    113        private long writeEntryHeader( final ArArchiveEntry pEntry ) throws IOException {
    114
    115            long offset = 0;
    116
    117            final String n = pEntry.getName();
    118            if (n.length() > 16) {
    119                throw new IOException("filename too long, > 16 chars: "+n);
    120            }
    121            offset += write(n);
    122
    123            offset = fill(offset, 16, ' ');
    124            final String m = "" + (pEntry.getLastModified() / 1000);
    125            if (m.length() > 12) {
    126                throw new IOException("modified too long");
    127            }
    128            offset += write(m);
    129
    130            offset = fill(offset, 28, ' ');
    131            final String u = "" + pEntry.getUserId();
    132            if (u.length() > 6) {
    133                throw new IOException("userid too long");
    134            }
    135            offset += write(u);
    136
    137            offset = fill(offset, 34, ' ');
    138            final String g = "" + pEntry.getGroupId();
    139            if (g.length() > 6) {
    140                throw new IOException("groupid too long");
    141            }
    142            offset += write(g);
    143
    144            offset = fill(offset, 40, ' ');
    145            final String fm = "" + Integer.toString(pEntry.getMode(), 8);
    146            if (fm.length() > 8) {
    147                throw new IOException("filemode too long");
    148            }
    149            offset += write(fm);
    150
    151            offset = fill(offset, 48, ' ');
    152            final String s = "" + pEntry.getLength();
    153            if (s.length() > 10) {
    154                throw new IOException("size too long");
    155            }
    156            offset += write(s);
    157
    158            offset = fill(offset, 58, ' ');
    159
    160            offset += write(ArArchiveEntry.TRAILER);
    161
    162            return offset;
    163        }
    164
    165        public void write(byte[] b, int off, int len) throws IOException {
    166            out.write(b, off, len);
    167            count(len);
    168            entryOffset += len;
    169        }
    170
    171        public void close() throws IOException {
    172            if(!finished) {
    173                finish();
    174            }
    175            out.close();
    176            prevEntry = null;
    177        }
    178
    179        public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
    180                throws IOException {
    181            if(finished) {
    182                throw new IOException("Stream has already been finished");
    183            }
    184            return new ArArchiveEntry(inputFile, entryName);
    185        }
    186
    187        /* (non-Javadoc)
    188         * @see org.apache.commons.compress.archivers.ArchiveOutputStream#finish()
    189         */
    190        public void finish() throws IOException {
    191            if(haveUnclosedEntry) {
    192                throw new IOException("This archive contains unclosed entries.");
    193            } else if(finished) {
    194                throw new IOException("This archive has already been finished");
    195            }
    196            finished = true;
    197        }
    198    }