File: chunker.rb

package info (click to toggle)
ruby-httpx 1.7.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,816 kB
  • sloc: ruby: 12,209; makefile: 4
file content (115 lines) | stat: -rw-r--r-- 2,715 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# frozen_string_literal: true

require "forwardable"

module HTTPX::Transcoder
  module Chunker
    class Error < HTTPX::Error; end

    CRLF = "\r\n".b

    class Encoder
      extend Forwardable

      def initialize(body)
        @raw = body
      end

      def each
        return enum_for(__method__) unless block_given?

        @raw.each do |chunk|
          yield "#{chunk.bytesize.to_s(16)}#{CRLF}#{chunk}#{CRLF}"
        end
        yield "0#{CRLF}"
      end

      def respond_to_missing?(meth, *args)
        @raw.respond_to?(meth, *args) || super
      end
    end

    class Decoder
      extend Forwardable

      def_delegator :@buffer, :empty?

      def_delegator :@buffer, :<<

      def_delegator :@buffer, :clear

      def initialize(buffer, trailers = false)
        @buffer = buffer
        @chunk_buffer = "".b
        @finished = false
        @state = :length
        @trailers = trailers
      end

      def to_s
        @buffer
      end

      def each
        loop do
          case @state
          when :length
            index = @buffer.index(CRLF)
            return unless index && index.positive?

            # Read hex-length
            hexlen = @buffer.byteslice(0, index)
            @buffer = @buffer.byteslice(index..-1) || "".b
            hexlen[/\h/] || raise(Error, "wrong chunk size line: #{hexlen}")
            @chunk_length = hexlen.hex
            # check if is last chunk
            @finished = @chunk_length.zero?
            nextstate(:crlf)
          when :crlf
            crlf_size = @finished && !@trailers ? 4 : 2
            # consume CRLF
            return if @buffer.bytesize < crlf_size
            raise Error, "wrong chunked encoding format" unless @buffer.start_with?(CRLF * (crlf_size / 2))

            @buffer = @buffer.byteslice(crlf_size..-1)
            if @chunk_length.nil?
              nextstate(:length)
            else
              return if @finished

              nextstate(:data)
            end
          when :data
            chunk = @buffer.byteslice(0, @chunk_length)
            @buffer = @buffer.byteslice(@chunk_length..-1) || "".b
            @chunk_buffer << chunk
            @chunk_length -= chunk.bytesize
            if @chunk_length.zero?
              yield @chunk_buffer unless @chunk_buffer.empty?
              @chunk_buffer.clear
              @chunk_length = nil
              nextstate(:crlf)
            end
          end
          break if @buffer.empty?
        end
      end

      def finished?
        @finished
      end

      private

      def nextstate(state)
        @state = state
      end
    end

    module_function

    def encode(chunks)
      Encoder.new(chunks)
    end
  end
end