001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.parser;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022
023/**
024 * Input stream meant to be used as input for stream-based parsers.
025 *
026 * <p>
027 * Keeps track of current byte position.
028 *
029 * <h5 class='section'>Notes:</h5><ul>
030 *    <li class='warn'>This class is not thread safe.
031 * </ul>
032 *
033 * <h5 class='section'>See Also:</h5><ul>
034 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
035 * </ul>
036 */
037@SuppressWarnings({
038   "java:S4929", // read(byte[],int,int) is intentionally omitted; ParserInputStream reads one byte at a time to maintain accurate position tracking for parse error reporting
039   "resource" // is field is owned by the ParserPipe; its lifetime is managed by the caller's session.
040})
041public class ParserInputStream extends InputStream implements Positionable {
042
043   private final InputStream is;
044   int pos = 0;
045   // Default cap (16 MiB) for wire-declared lengths; overridable via setMaxLength.
046   private int maxLength = 16 * 1024 * 1024;
047
048   /**
049    * Constructor.
050    *
051    * @param pipe The parser input.
052    *    <br>Must not be <jk>null</jk>.
053    * @throws IOException Thrown by underlying stream.
054    */
055   protected ParserInputStream(ParserPipe pipe) throws IOException {
056      this.is = pipe.getInputStream();
057      pipe.setPositionable(this);
058   }
059
060   /**
061    * Sets the maximum allowed wire-declared length (in bytes) for payloads and element counts read off the
062    * stream before they are used to size an allocation or drive a container loop.
063    *
064    * <p>
065    * Guards against malformed input where a small payload declares a huge or negative length that would
066    * otherwise trigger {@link OutOfMemoryError} or {@link NegativeArraySizeException}.
067    *
068    * @param value The maximum length in bytes.  Values &le; 0 disable the cap (only the negative-length check remains).
069    */
070   public void setMaxLength(int value) {
071      maxLength = value <= 0 ? Integer.MAX_VALUE : value;
072   }
073
074   /**
075    * Validates a wire-declared length/count against sane bounds before it is used to size an allocation or
076    * drive a container loop, using this stream's configured maximum.
077    *
078    * @param len The declared length/count read off the wire.
079    * @param what A short description of the field being read (for the error message).
080    * @return The validated length as an int.
081    * @throws IOException If the length is negative (or beyond int range) or exceeds the configured maximum.
082    */
083   public int checkLength(long len, String what) throws IOException {
084      return checkLength(len, maxLength, what);
085   }
086
087   /**
088    * Validates a wire-declared length/count against sane bounds before it is used to size an allocation or
089    * drive a container loop.
090    *
091    * <p>
092    * Shared bounds-check used by the stream-based parsers and by other low-level readers (such as the
093    * Protobuf reader) that do not extend this class but need the same guard.
094    *
095    * @param len The declared length/count read off the wire.
096    * @param maxLength The maximum allowed length.  Values &le; 0 are treated as {@link Integer#MAX_VALUE}
097    *    (only the negative-length check applies).
098    * @param what A short description of the field being read (for the error message).
099    * @return The validated length as an int.
100    * @throws IOException If the length is negative (or beyond int range) or exceeds the configured maximum.
101    */
102   public static int checkLength(long len, long maxLength, String what) throws IOException {
103      var max = maxLength <= 0 ? Integer.MAX_VALUE : maxLength;
104      if (len < 0)
105         throw ioex("Invalid %s length (negative): %s", what, len);
106      if (len > max)
107         throw ioex("%s length %s exceeds maximum allowed %s", what, len, max);
108      return (int)len;
109   }
110
111   @Override /* Overridden from Positionable */
112   public Position getPosition() { return new Position(pos); }
113
114   @Override /* Overridden from InputStream */
115   public int read() throws IOException {
116      int i = is.read();
117      if (i > 0)
118         pos++;
119      return i;
120   }
121}