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}