001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.util;
019
020
021import com.nimbusds.jose.JWEObject;
022
023import java.io.ByteArrayInputStream;
024import java.io.ByteArrayOutputStream;
025import java.io.IOException;
026import java.util.Objects;
027import java.util.zip.Deflater;
028import java.util.zip.DeflaterOutputStream;
029import java.util.zip.Inflater;
030import java.util.zip.InflaterInputStream;
031
032
033/**
034 * Deflate (RFC 1951) utilities.
035 *
036 * @author Vladimir Dzhuvinov
037 * @version 2016-09-16
038 */
039public class DeflateUtils {
040
041
042        /**
043         * Omit headers and CRC fields from output, as specified by RFC 1950.
044         * Note that the Deflater JavaDocs are incorrect, see
045         * http://stackoverflow.com/questions/11076060/decompressing-gzipped-data-with-inflater-in-java
046         */
047        private static final boolean NOWRAP = true;
048
049
050        /**
051         * Compresses the specified byte array according to the DEFLATE 
052         * specification (RFC 1951).
053         *
054         * @param bytes The byte array to compress. Must not be {@code null}.
055         *
056         * @return The compressed bytes.
057         *
058         * @throws IOException If compression failed.
059         */
060        public static byte[] compress(final byte[] bytes)
061                throws IOException {
062
063                Objects.requireNonNull(bytes);
064
065                ByteArrayOutputStream out = new ByteArrayOutputStream();
066
067                Deflater deflater = new Deflater(Deflater.DEFLATED, NOWRAP);
068                try (DeflaterOutputStream def = new DeflaterOutputStream(out, deflater)) {
069                        def.write(bytes);
070                } finally {
071                        deflater.end();
072                }
073
074                return out.toByteArray();
075        }
076
077
078        /**
079         * Decompresses the specified byte array according to the DEFLATE
080         * specification (RFC 1951).
081         *
082         * @param bytes The byte array to decompress. Must not be {@code null}.
083         *
084         * @return The decompressed bytes.
085         *
086         * @throws IOException If decompression failed.
087         */
088        public static byte[] decompress(final byte[] bytes)
089                        throws IOException {
090
091                return decompress(bytes, JWEObject.MAX_DECOMPRESSED_PLAIN_TEXT_LENGTH);
092        }
093
094
095        /**
096         * Decompresses the specified byte array according to the DEFLATE
097         * specification (RFC 1951), subject to a maximum output length.
098         *
099         * @param bytes The byte array to decompress. Must not be {@code null}.
100         * @param maxLength The maximum allowed output length, in bytes. Must be
101         *                  positive.
102         *
103         * @return The decompressed bytes.
104         *
105         * @throws IOException If decompression failed or the decompressed output
106         *                     exceeds the specified maximum length.
107         */
108        public static byte[] decompress(final byte[] bytes, final int maxLength)
109                        throws IOException {
110
111                Objects.requireNonNull(bytes);
112
113                if (maxLength <= 0) {
114                        throw new IllegalArgumentException("The maximum decompressed length must be a positive integer");
115                }
116
117                // Heuristic initial capacity: compressed size * 2 (typical compression ratio),
118                // bounded between 1 KB and maxLength to avoid excessive pre-allocation.
119                int initialCapacity = (int) Math.min(maxLength, Math.max((long) bytes.length * 2, 1024L));
120
121                Inflater inflater = new Inflater(NOWRAP);
122                try (InflaterInputStream inf = new InflaterInputStream(new ByteArrayInputStream(bytes), inflater)) {
123                        ByteArrayOutputStream out = new ByteArrayOutputStream(initialCapacity);
124
125                        // Transfer bytes from the compressed array to the output
126                        byte[] buf = new byte[Math.min(maxLength, 4096)];
127
128                        int len;
129
130                        while ((len = inf.read(buf)) != -1) {
131                                if ((long) out.size() + len > maxLength) {
132                                        throw new IOException("Decompressed plain text exceeds the maximum allowed length of " + maxLength + " bytes");
133                                }
134
135                                out.write(buf, 0, len);
136                        }
137
138                        return out.toByteArray();
139                } finally {
140                        inflater.end();
141                }
142        }
143
144
145        /**
146         * Prevents public instantiation.
147         */
148        private DeflateUtils() {
149
150        }
151}