Put copyright text in new files, include GPL2 text
[unicorn.git] / lib / unicorn / chunked_reader.rb
bloba16a4e5eb931d8705a37bf118eae48c37fde7639
1 # Copyright (c) 2009 Eric Wong
2 # You can redistribute it and/or modify it under the same terms as Ruby.
4 module Unicorn; end
6 module Unicorn
7   class ChunkedReader
9     Z = ''
10     Z.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding)
12     def initialize
13       @input = @buf = nil
14       @chunk_left = 0
15     end
17     def reopen(input, buf)
18       buf ||= Z.dup
19       buf.force_encoding(Encoding::BINARY) if buf.respond_to?(:force_encoding)
20       @input, @buf = input, buf
21       parse_chunk_header
22       self
23     end
25     def readpartial(max, buf = Z.dup)
26       buf.force_encoding(Encoding::BINARY) if buf.respond_to?(:force_encoding)
28       while @input && @chunk_left <= 0 && ! parse_chunk_header
29         @buf << @input.readpartial(Const::CHUNK_SIZE, buf)
30       end
32       if @input
33         begin
34           @buf << @input.read_nonblock(Const::CHUNK_SIZE, buf)
35         rescue Errno::EAGAIN, Errno::EINTR
36         end
37       end
39       max = @chunk_left if max > @chunk_left
40       buf.replace(last_block(max) || Z)
41       @chunk_left -= buf.size
42       (0 == buf.size && @input.nil?) and raise EOFError
43       buf
44     end
46     def gets
47       line = nil
48       begin
49         line = readpartial(Const::CHUNK_SIZE)
50         begin
51           if line.sub!(%r{\A(.*?#{$/})}, Z)
52             @chunk_left += line.size
53             @buf = @buf ? (line << @buf) : line
54             return $1.dup
55           end
56           line << readpartial(Const::CHUNK_SIZE)
57         end while true
58       rescue EOFError
59         return line
60       end
61     end
63   private
65     def last_block(max = nil)
66       rv = @buf
67       if max && rv && max < rv.size
68         @buf = rv[max - rv.size, rv.size - max]
69         return rv[0, max]
70       end
71       @buf = Z.dup
72       rv
73     end
75     def parse_chunk_header
76       buf = @buf
77       # ignoring chunk-extension info for now, I haven't seen any use for it
78       # (or any users, and TE:chunked sent by clients is rare already)
79       # if there was not enough data in buffer to parse length of the chunk
80       # then just return
81       if buf.sub!(/\A(?:\r\n)?([a-fA-F0-9]{1,8})[^\r]*?\r\n/, Z)
82         @chunk_left = $1.to_i(16)
83         if 0 == @chunk_left # EOF
84           buf.sub!(/\A\r\n(?:\r\n)?/, Z) # cleanup for future requests
85           @input = nil
86         end
87         return @chunk_left
88       end
90       buf.size > 256 and
91           raise HttpParserError,
92                 "malformed chunk, chunk-length not found in buffer: " \
93                 "#{buf.inspect}"
94       nil
95     end
97   end
99 end