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}