001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 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 java.io.IOException; 022import java.io.InputStream; 023 024 025/** 026 * Size-bounded input stream. Adapted from Apache Commons IO. Throws an 027 * {@link IOException} if the input size limit is exceeded. 028 * 029 * @version 2016-11-28 030 */ 031public class BoundedInputStream extends InputStream { 032 033 034 /** 035 * The wrapped input stream. 036 */ 037 private final InputStream in; 038 039 040 /** 041 * The limit, -1 if none. 042 */ 043 private final long max; 044 045 046 /** 047 * The current input stream position. 048 */ 049 private long pos; 050 051 052 /** 053 * Marks the input stream. 054 */ 055 private long mark; 056 057 058 /** 059 * If {@link #close()} is to be propagated to the underlying input 060 * stream. 061 */ 062 private boolean propagateClose; 063 064 065 /** 066 * Creates a new bounded input stream. 067 * 068 * @param in The input stream to wrap. 069 * @param size The maximum number of bytes to return, -1 if no limit. 070 */ 071 public BoundedInputStream(final InputStream in, final long size) { 072 this.pos = 0L; 073 this.mark = -1L; 074 this.propagateClose = true; 075 this.max = size; 076 this.in = in; 077 } 078 079 080 /** 081 * Creates a new unbounded input stream. 082 * 083 * @param in The input stream to wrap. 084 */ 085 public BoundedInputStream(final InputStream in) { 086 this(in, -1L); 087 } 088 089 090 /** 091 * Returns the maximum number of bytes to return. 092 * 093 * @return The maximum number of bytes to return, -1 if no limit. 094 */ 095 public long getLimitBytes() { 096 return max; 097 } 098 099 100 @Override 101 public int read() throws IOException { 102 if (this.max >= 0L && this.pos >= this.max) { 103 throw new IOException("Exceeded configured input limit of " + this.max + " bytes"); 104 } else { 105 int result = this.in.read(); 106 ++this.pos; 107 return result; // data or -1 on EOF 108 } 109 } 110 111 112 @Override 113 public int read(byte[] b) throws IOException { 114 return this.read(b, 0, b.length); 115 } 116 117 118 @Override 119 public int read(byte[] b, int off, int len) throws IOException { 120 if(this.max >= 0L && this.pos >= this.max) { 121 throw new IOException("Exceeded configured input limit of " + this.max + " bytes"); 122 } else { 123 int bytesRead = this.in.read(b, off, len); 124 125 if(bytesRead == -1) { 126 return -1; 127 } else { 128 this.pos += (long)bytesRead; 129 130 if (this.max >= 0L && this.pos >= this.max) 131 throw new IOException("Exceeded configured input limit of " + this.max + " bytes"); 132 133 return bytesRead; 134 } 135 } 136 } 137 138 139 @Override 140 public long skip(long n) throws IOException { 141 long toSkip = this.max >= 0L?Math.min(n, this.max - this.pos):n; 142 long skippedBytes = this.in.skip(toSkip); 143 this.pos += skippedBytes; 144 return skippedBytes; 145 } 146 147 148 @Override 149 public int available() throws IOException { 150 return this.max >= 0L && this.pos >= this.max?0:this.in.available(); 151 } 152 153 154 @Override 155 public String toString() { 156 return this.in.toString(); 157 } 158 159 160 @Override 161 public void close() throws IOException { 162 if(this.propagateClose) { 163 this.in.close(); 164 } 165 } 166 167 168 @Override 169 public synchronized void reset() throws IOException { 170 this.in.reset(); 171 this.pos = this.mark; 172 } 173 174 175 @Override 176 public synchronized void mark(int readlimit) { 177 this.in.mark(readlimit); 178 this.mark = this.pos; 179 } 180 181 182 @Override 183 public boolean markSupported() { 184 return this.in.markSupported(); 185 } 186 187 188 /** 189 * Indicates whether the {@link #close()} method should propagate to 190 * the underling InputStream. 191 * 192 * @return {@code true} if calling {@link #close()} propagates to the 193 * {@link #close()} method of the underlying stream or 194 * {@code false} if it does not. 195 */ 196 public boolean isPropagateClose() { 197 return this.propagateClose; 198 } 199 200 201 /** 202 * Set whether the {@link #close()} method should propagate to the 203 * underling InputStream. 204 * 205 * @param propagateClose {@code true} if calling {@link #close()} 206 * propagates to the {@link #close()} method of 207 * the underlying stream or {@code false} if it 208 * does not. 209 */ 210 public void setPropagateClose(boolean propagateClose) { 211 this.propagateClose = propagateClose; 212 } 213}