MAX_BIN_SIZE => MAX_BINARY_SIZE, tests for overflow
[sunshowers.git] / lib / sunshowers / io.rb
blobea45c4d331a56381a1d11436586671b0be3a4d46
1 # -*- encoding: binary -*-
3 module Sunshowers
5   class IO < Struct.new(:to_io, :buf)
6     # most Web Socket messages from clients are expected to be small text
7     RD_SIZE = 128
9     # maximum size of a UTF-8 buffer we'll allow in memory
10     # this is a soft limit and may be offset by the value of RD_SIZE
11     MAX_UTF8_SIZE = 1024 * 16
13     # maximum size of a binary buffer we'll allow in memory
14     # this is a soft limit and may be offset by the value of RD_SIZE
15     MAX_BINARY_SIZE = 1024 * 112
17     # Web Sockets usually uses UTF-8 when interfacing with the client
18     # Ruby Sockets always return strings of Encoding::Binary
19     ENC = defined?(Encoding::UTF_8) ? Encoding::UTF_8 : nil
21     Z = ""
23     def initialize(io, buf = Z.dup)
24       super
25     end
27     # iterates through each message until a client the connection is closed
28     def each(&block)
29       while str = gets
30         yield str
31       end
32       self
33     end
35     # retrieves the next record, returns nil if client closes connection
36     def gets
37       begin
38         unless buf.empty?
39           buf.gsub!(/\A\x00(.*?)\xff/m, Z) and return utf8!($1)
40           rv = read_binary and return rv
41           buf.size > MAX_UTF8_SIZE and
42             raise ProtocolError, "buffer too large #{buf.size}"
43         end
44         buf << read
45       rescue EOFError
46         return
47       end while true
48     end
50     def syswrite(buf)
51       to_io.write(buf)
52       rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,
53              Errno::EINVAL,Errno::EBADF => e
54         raise ClientShutdown, e.message, []
55     end
57     def write_utf8(buf)
58       syswrite("\0#{binary(buf)}\xff")
59     end
61     def write_binary(buf)
62       buf = binary(buf)
63       n = buf.size
64       length = []
65       begin
66         length.unshift((n % 128) | 0x80)
67       end while (n /= 128) > 0
68       length[-1] ^= 0x80
70       syswrite("\x80#{length.pack("C*")}#{buf}")
71     end
73     if ENC.nil?
74       def valid_utf8?(buf)
75         buf.unpack("U*")
76         true
77         rescue ArgumentError
78           false
79       end
81       def write(buf)
82         valid_utf8?(buf) ? write_utf8(buf) : write_binary(buf)
83       end
84     else
85       def write(buf)
86         buf.encoding == ENC ? write_utf8(buf) : write_binary(buf)
87       end
88     end
90     # :stopdoc:
91     def read(size = nil)
92       i = to_io
93       # read with no args for Revactor compat
94       i.respond_to?(:readpartial) ?
95         i.readpartial(size.nil? ? RD_SIZE : size) :
96         i.read(size.nil? ? nil : size)
97     end
99     if ENC.nil?
100       def utf8!(buf)
101         valid_utf8?(buf) or raise ProtocolError, "not UTF-8: #{buf.inspect}"
102         buf
103       end
104       def binary(buf); buf; end
105     else
106       def utf8!(buf)
107         buf.force_encoding(ENC)
108         buf.valid_encoding? or raise ProtocolError, "not UTF-8: #{buf.inspect}"
109         buf
110       end
111       def binary(buf)
112         buf.encoding == Encoding::BINARY ?
113           buf : buf.dup.force_encoding(Encoding::BINARY)
114       end
115     end
117     if Z.respond_to?(:ord)
118       def ord(byte_str); byte_str.ord; end
119     else
120       def ord(byte_str); byte_str; end
121     end
123     def read_binary
124       (ord(buf[0]) & 0x80) == 0x80 or return
126       i = 1
127       b = length = 0
128       begin
129         buf << read while (b = buf[i]).nil?
130         b = ord(b)
131         length = length * 128 + (b & 0x7f)
132         i += 1
133       end while (b & 0x80) != 0
135       length > MAX_BINARY_SIZE and
136         raise ProtocolError, "chunk too large: #{length} bytes"
138       to_read = length - buf.size + i
139       while to_read > 0
140         buf << (tmp = read)
141         to_read -= tmp.size
142       end
143       rv = buf[i, length]
144       buf.replace(buf[i+length, buf.size])
145       rv
146     end
148   end