View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2005, 2013
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
12   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
13   *  
14   * Change History
15   * Flag       Date        Prog         Description
16   *------------------------------------------------------------------------------- 
17   * 13799      2004-12-07  thschaef     Fixes on chunking
18   * 1535756    2006-08-07  lupusalex    Make code warning free
19   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
20   * 1660575    2007-02-15  lupusalex    Chunking broken on SUN JRE
21   * 1688273    2007-04-16  ebak         Full support of HTTP trailers
22   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
23   * 2204488 	  2008-10-28  raman_arora  Fix code to remove compiler warnings
24   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
25   * 3304058    2011-05-20  blaschke-oss Use same date format in change history
26   * 3557283    2012-11-05  blaschke-oss Print full response when get EOF from CIMOM
27   * 3601894    2013-01-23  blaschke-oss Enhance HTTP and CIM-XML tracing
28   *    2621    2013-02-23  blaschke-oss Not all chunked input has trailers
29   *    2709    2013-11-13  blaschke-oss Lower the level of the EOF message to FINE
30   */
31  package org.metricshub.wbem.sblim.cimclient.internal.http.io;
32  
33  /*-
34   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
35   * WBEM Java Client
36   * ჻჻჻჻჻჻
37   * Copyright 2023 - 2025 MetricsHub
38   * ჻჻჻჻჻჻
39   * Licensed under the Apache License, Version 2.0 (the "License");
40   * you may not use this file except in compliance with the License.
41   * You may obtain a copy of the License at
42   *
43   *      http://www.apache.org/licenses/LICENSE-2.0
44   *
45   * Unless required by applicable law or agreed to in writing, software
46   * distributed under the License is distributed on an "AS IS" BASIS,
47   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
48   * See the License for the specific language governing permissions and
49   * limitations under the License.
50   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
51   */
52  
53  import java.io.EOFException;
54  import java.io.IOException;
55  import java.io.InputStream;
56  import java.util.logging.Level;
57  import org.metricshub.wbem.sblim.cimclient.internal.http.HttpHeader;
58  import org.metricshub.wbem.sblim.cimclient.internal.http.HttpMethod;
59  import org.metricshub.wbem.sblim.cimclient.internal.logging.LogAndTraceBroker;
60  
61  /**
62   * Class ChunkedInputStream implements an input stream for chunked messages
63   *
64   */
65  public class ChunkedInputStream extends InputStream {
66  	private InputStream iIn;
67  
68  	private String iTrailerFields;
69  
70  	private String iOrigin;
71  
72  	private long iChunkSize = 0;
73  
74  	private boolean iEof = false;
75  
76  	private HttpHeader iTrailers = new HttpHeader();
77  
78  	private boolean iClosed = false;
79  
80  	private byte[] iTmp = new byte[1];
81  
82  	/**
83  	 * Ctor.
84  	 *
85  	 * @param pStream
86  	 *            The stream to create this one upon
87  	 * @param pTrailerFields
88  	 *            The names of trailer fields
89  	 */
90  	public ChunkedInputStream(InputStream pStream, String pTrailerFields) {
91  		this(pStream, pTrailerFields, null);
92  	}
93  
94  	/**
95  	 * Ctor.
96  	 *
97  	 * @param pStream
98  	 *            The stream to create this one upon
99  	 * @param pTrailerFields
100 	 *            The names of trailer fields
101 	 * @param pOrigin
102 	 *            The origin of the stream (response, indication request, etc.)
103 	 */
104 	public ChunkedInputStream(InputStream pStream, String pTrailerFields, String pOrigin) {
105 		this.iIn = pStream;
106 		this.iTrailerFields = pTrailerFields;
107 		this.iOrigin = pOrigin == null ? "Unknown" : pOrigin;
108 	}
109 
110 	@Override
111 	public synchronized int read() throws IOException {
112 		return (read(this.iTmp, 0, 1) > 0) ? (this.iTmp[0] & 0xFF) : -1;
113 	}
114 
115 	@Override
116 	public synchronized int read(byte[] buf, int off, int len) throws IOException {
117 		int total = 0;
118 		if (this.iEof || this.iClosed) return -1; // 13799 (return -1 if closed,
119 		// not if !closed)
120 
121 		if (this.iChunkSize == 0) {
122 			String line = HttpMethod.readLine(this.iIn); // TODO read line using
123 			// valid character encoding
124 
125 			if ("".equals(line)) {
126 				// 13799 The chunked data is ending with
127 				// CRLF, so the first line read after it
128 				// results ""
129 				line = HttpMethod.readLine(this.iIn);
130 				// 13799 Except first chunk, the
131 				// above only read the CRLF !
132 			}
133 			// TODO - get rid of ";*" suffix
134 			try {
135 				this.iChunkSize = Long.parseLong(line, 16);
136 			} catch (Exception e) {
137 				LogAndTraceBroker.getBroker().trace(Level.FINER, "Invalid chunk size on HTTP stream", e);
138 				this.iEof = true;
139 				throw new IOException("Invalid chunk size");
140 			}
141 		}
142 		if (this.iChunkSize > 0) {
143 			total = this.iIn.read(buf, off, (this.iChunkSize < len) ? (int) this.iChunkSize : (int) len);
144 			if (total > 0) {
145 				this.iChunkSize -= total;
146 			}
147 			if (total == -1) {
148 				LogAndTraceBroker
149 					.getBroker()
150 					.trace(
151 						Level.FINE,
152 						"Unexpected EOF trying to read " +
153 						(this.iChunkSize < len ? this.iChunkSize : len) +
154 						" bytes from HTTP chunk with remaining length of " +
155 						this.iChunkSize
156 					);
157 				throw new EOFException("Unexpected EOF reading chunk");
158 			}
159 		} else {
160 			// read trailer
161 			this.iEof = true;
162 			if (this.iTrailerFields != null && this.iTrailerFields.trim().length() > 0) {
163 				try {
164 					this.iTrailers = new HttpHeader(this.iIn);
165 					// ebak: http trailers
166 					this.iTrailers.examineTrailer(this.iOrigin);
167 				} catch (IOException e) {
168 					LogAndTraceBroker
169 						.getBroker()
170 						.trace(Level.FINE, "Unexpected EOF reading trailer, expected fields were " + this.iTrailerFields);
171 					throw new EOFException("Unexpected EOF reading trailer");
172 				}
173 			}
174 		}
175 		return total > 0 ? total : -1;
176 	}
177 
178 	/**
179 	 * Return the http header trailers
180 	 *
181 	 * @return The trailers
182 	 */
183 	public synchronized HttpHeader getTrailers() {
184 		return this.iTrailers;
185 	}
186 
187 	@Override
188 	public synchronized long skip(long total) throws IOException {
189 		byte[] tmp = new byte[(int) total];
190 		return read(tmp, 0, (int) total);
191 	}
192 
193 	/**
194 	 * @return int
195 	 *
196 	 */
197 	@Override
198 	public synchronized int available() {
199 		return (this.iEof ? 0 : (this.iChunkSize > 0 ? (int) this.iChunkSize : 1));
200 	}
201 
202 	@Override
203 	public void close() throws IOException {
204 		if (!this.iClosed) {
205 			this.iClosed = true;
206 			byte[] buf = new byte[512];
207 			while (read(buf, 0, buf.length) > -1) {
208 				// empty
209 			}
210 			this.iIn.close();
211 		} else throw new IOException("Error while closing stream");
212 	}
213 }