001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.nio.reactor;
029
030import java.io.IOException;
031import java.nio.ByteBuffer;
032import java.nio.CharBuffer;
033import java.nio.channels.ReadableByteChannel;
034import java.nio.channels.WritableByteChannel;
035import java.nio.charset.CharacterCodingException;
036import java.nio.charset.Charset;
037import java.nio.charset.CharsetDecoder;
038import java.nio.charset.CoderResult;
039import java.nio.charset.CodingErrorAction;
040
041import org.apache.http.MessageConstraintException;
042import org.apache.http.config.MessageConstraints;
043import org.apache.http.nio.reactor.SessionInputBuffer;
044import org.apache.http.nio.util.ByteBufferAllocator;
045import org.apache.http.nio.util.ExpandableBuffer;
046import org.apache.http.nio.util.HeapByteBufferAllocator;
047import org.apache.http.params.CoreProtocolPNames;
048import org.apache.http.params.HttpParams;
049import org.apache.http.protocol.HTTP;
050import org.apache.http.util.Args;
051import org.apache.http.util.CharArrayBuffer;
052import org.apache.http.util.CharsetUtils;
053
054/**
055 * Default implementation of {@link SessionInputBuffer} based on
056 * the {@link ExpandableBuffer} class.
057 *
058 * @since 4.0
059 */
060@SuppressWarnings("deprecation")
061public class SessionInputBufferImpl extends ExpandableBuffer implements SessionInputBuffer {
062
063    private final CharsetDecoder chardecoder;
064    private final MessageConstraints constraints;
065    private final int lineBuffersize;
066
067    private CharBuffer charbuffer;
068
069    /**
070     *  Creates SessionInputBufferImpl instance.
071     *
072     * @param buffersize input buffer size
073     * @param lineBuffersize buffer size for line operations. Has effect only if
074     *   {@code chardecoder} is not {@code null}.
075     * @param chardecoder chardecoder to be used for decoding HTTP protocol elements.
076     *   If {@code null} simple type cast will be used for byte to char conversion.
077     * @param constraints Message constraints. If {@code null}
078     *   {@link MessageConstraints#DEFAULT} will be used.
079     * @param allocator memory allocator.
080     *   If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used.
081     *
082     * @since 4.4
083     */
084    public SessionInputBufferImpl(
085            final int buffersize,
086            final int lineBuffersize,
087            final MessageConstraints constraints,
088            final CharsetDecoder chardecoder,
089            final ByteBufferAllocator allocator) {
090        super(buffersize, allocator != null ? allocator : HeapByteBufferAllocator.INSTANCE);
091        this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
092        this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
093        this.chardecoder = chardecoder;
094    }
095
096    /**
097     *  Creates SessionInputBufferImpl instance.
098     *
099     * @param buffersize input buffer size
100     * @param lineBuffersize buffer size for line operations. Has effect only if
101     *   {@code chardecoder} is not {@code null}.
102     * @param chardecoder chardecoder to be used for decoding HTTP protocol elements.
103     *   If {@code null} simple type cast will be used for byte to char conversion.
104     * @param allocator memory allocator.
105     *   If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used.
106     *
107     * @since 4.3
108     */
109    public SessionInputBufferImpl(
110            final int buffersize,
111            final int lineBuffersize,
112            final CharsetDecoder chardecoder,
113            final ByteBufferAllocator allocator) {
114        this(buffersize, lineBuffersize, null, chardecoder, allocator);
115    }
116
117    /**
118     * @deprecated (4.3) use
119     *   {@link SessionInputBufferImpl#SessionInputBufferImpl(int, int, CharsetDecoder,
120     *     ByteBufferAllocator)}
121     */
122    @Deprecated
123    public SessionInputBufferImpl(
124            final int buffersize,
125            final int lineBuffersize,
126            final ByteBufferAllocator allocator,
127            final HttpParams params) {
128        super(buffersize, allocator);
129        this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
130        final String charsetName = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET);
131        final Charset charset = CharsetUtils.lookup(charsetName);
132        if (charset != null) {
133            this.chardecoder = charset.newDecoder();
134            final CodingErrorAction a1 = (CodingErrorAction) params.getParameter(
135                    CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION);
136            this.chardecoder.onMalformedInput(a1 != null ? a1 : CodingErrorAction.REPORT);
137            final CodingErrorAction a2 = (CodingErrorAction) params.getParameter(
138                    CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION);
139            this.chardecoder.onUnmappableCharacter(a2 != null? a2 : CodingErrorAction.REPORT);
140        } else {
141            this.chardecoder = null;
142        }
143        this.constraints = MessageConstraints.DEFAULT;
144    }
145
146    /**
147     * @deprecated (4.3) use
148     *   {@link SessionInputBufferImpl#SessionInputBufferImpl(int, int, Charset)}
149     */
150    @Deprecated
151    public SessionInputBufferImpl(
152            final int buffersize,
153            final int linebuffersize,
154            final HttpParams params) {
155        this(buffersize, linebuffersize, HeapByteBufferAllocator.INSTANCE, params);
156    }
157
158    /**
159     * @since 4.3
160     */
161    public SessionInputBufferImpl(
162            final int buffersize,
163            final int lineBuffersize,
164            final Charset charset) {
165        this(buffersize, lineBuffersize, null,
166                charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE);
167    }
168
169    /**
170     * @since 4.3
171     */
172    public SessionInputBufferImpl(
173            final int buffersize,
174            final int lineBuffersize,
175            final MessageConstraints constraints,
176            final Charset charset) {
177        this(buffersize, lineBuffersize, constraints,
178                charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE);
179    }
180
181    /**
182     * @since 4.3
183     */
184    public SessionInputBufferImpl(
185            final int buffersize,
186            final int lineBuffersize) {
187        this(buffersize, lineBuffersize, null, null, HeapByteBufferAllocator.INSTANCE);
188    }
189
190    /**
191     * @since 4.3
192     */
193    public SessionInputBufferImpl(final int buffersize) {
194        this(buffersize, 256, null, null, HeapByteBufferAllocator.INSTANCE);
195    }
196
197    @Override
198    public int fill(final ReadableByteChannel channel) throws IOException {
199        Args.notNull(channel, "Channel");
200        setInputMode();
201        if (!this.buffer.hasRemaining()) {
202            expand();
203        }
204        return channel.read(this.buffer);
205    }
206
207    @Override
208    public int read() {
209        setOutputMode();
210        return this.buffer.get() & 0xff;
211    }
212
213    @Override
214    public int read(final ByteBuffer dst, final int maxLen) {
215        if (dst == null) {
216            return 0;
217        }
218        setOutputMode();
219        final int len = Math.min(dst.remaining(), maxLen);
220        final int chunk = Math.min(this.buffer.remaining(), len);
221        if (this.buffer.remaining() > chunk) {
222            final int oldLimit = this.buffer.limit();
223            final int newLimit = this.buffer.position() + chunk;
224            this.buffer.limit(newLimit);
225            dst.put(this.buffer);
226            this.buffer.limit(oldLimit);
227            return len;
228        } else {
229            dst.put(this.buffer);
230        }
231        return chunk;
232    }
233
234    @Override
235    public int read(final ByteBuffer dst) {
236        if (dst == null) {
237            return 0;
238        }
239        return read(dst, dst.remaining());
240    }
241
242    @Override
243    public int read(final WritableByteChannel dst, final int maxLen) throws IOException {
244        if (dst == null) {
245            return 0;
246        }
247        setOutputMode();
248        final int bytesRead;
249        if (this.buffer.remaining() > maxLen) {
250            final int oldLimit = this.buffer.limit();
251            final int newLimit = oldLimit - (this.buffer.remaining() - maxLen);
252            this.buffer.limit(newLimit);
253            bytesRead = dst.write(this.buffer);
254            this.buffer.limit(oldLimit);
255        } else {
256            bytesRead = dst.write(this.buffer);
257        }
258        return bytesRead;
259    }
260
261    @Override
262    public int read(final WritableByteChannel dst) throws IOException {
263        if (dst == null) {
264            return 0;
265        }
266        setOutputMode();
267        return dst.write(this.buffer);
268    }
269
270    @Override
271    public boolean readLine(
272            final CharArrayBuffer linebuffer,
273            final boolean endOfStream) throws CharacterCodingException {
274
275        setOutputMode();
276        // See if there is LF char present in the buffer
277        int pos = -1;
278        for (int i = this.buffer.position(); i < this.buffer.limit(); i++) {
279            final int b = this.buffer.get(i);
280            if (b == HTTP.LF) {
281                pos = i + 1;
282                break;
283            }
284        }
285
286        final int maxLineLen = this.constraints.getMaxLineLength();
287        if (maxLineLen > 0) {
288            final int currentLen = (pos > 0 ? pos : this.buffer.limit()) - this.buffer.position();
289            if (currentLen >= maxLineLen) {
290                throw new MessageConstraintException("Maximum line length limit exceeded");
291            }
292        }
293
294        if (pos == -1) {
295            if (endOfStream && this.buffer.hasRemaining()) {
296                // No more data. Get the rest
297                pos = this.buffer.limit();
298            } else {
299                // Either no complete line present in the buffer
300                // or no more data is expected
301                return false;
302            }
303        }
304        final int origLimit = this.buffer.limit();
305        this.buffer.limit(pos);
306
307        final int requiredCapacity = this.buffer.limit() - this.buffer.position();
308        // Ensure capacity of len assuming ASCII as the most likely charset
309        linebuffer.ensureCapacity(requiredCapacity);
310
311        if (this.chardecoder == null) {
312            if (this.buffer.hasArray()) {
313                final byte[] b = this.buffer.array();
314                final int off = this.buffer.position();
315                final int len = this.buffer.remaining();
316                linebuffer.append(b, off, len);
317                this.buffer.position(off + len);
318            } else {
319                while (this.buffer.hasRemaining()) {
320                    linebuffer.append((char) (this.buffer.get() & 0xff));
321                }
322            }
323        } else {
324            if (this.charbuffer == null) {
325                this.charbuffer = CharBuffer.allocate(this.lineBuffersize);
326            }
327            this.chardecoder.reset();
328
329            for (;;) {
330                final CoderResult result = this.chardecoder.decode(
331                        this.buffer,
332                        this.charbuffer,
333                        true);
334                if (result.isError()) {
335                    result.throwException();
336                }
337                if (result.isOverflow()) {
338                    this.charbuffer.flip();
339                    linebuffer.append(
340                            this.charbuffer.array(),
341                            this.charbuffer.position(),
342                            this.charbuffer.remaining());
343                    this.charbuffer.clear();
344                }
345                if (result.isUnderflow()) {
346                    break;
347                }
348            }
349
350            // flush the decoder
351            this.chardecoder.flush(this.charbuffer);
352            this.charbuffer.flip();
353            // append the decoded content to the line buffer
354            if (this.charbuffer.hasRemaining()) {
355                linebuffer.append(
356                        this.charbuffer.array(),
357                        this.charbuffer.position(),
358                        this.charbuffer.remaining());
359            }
360
361        }
362        this.buffer.limit(origLimit);
363
364        // discard LF if found
365        int l = linebuffer.length();
366        if (l > 0) {
367            if (linebuffer.charAt(l - 1) == HTTP.LF) {
368                l--;
369                linebuffer.setLength(l);
370            }
371            // discard CR if found
372            if (l > 0) {
373                if (linebuffer.charAt(l - 1) == HTTP.CR) {
374                    l--;
375                    linebuffer.setLength(l);
376                }
377            }
378        }
379        return true;
380    }
381
382    @Override
383    public String readLine(final boolean endOfStream) throws CharacterCodingException {
384        final CharArrayBuffer buffer = new CharArrayBuffer(64);
385        final boolean found = readLine(buffer, endOfStream);
386        if (found) {
387            return buffer.toString();
388        } else {
389            return null;
390        }
391    }
392
393}