Class: BetterCap::Proxy::HTTP::Response

Inherits:
Object
  • Object
show all
Defined in:
lib/bettercap/proxy/http/response.rb

Overview

HTTP response parser.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeResponse

Initialize this response object state.


73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/bettercap/proxy/http/response.rb', line 73

def initialize
  @version = '1.1'
  @code = 200
  @status = 'OK'
  @content_type = nil
  @charset = 'UTF-8'
  @content_length = nil
  @body = nil
  @headers = {}
  @headers_done = false
  @chunked = false
end

Instance Attribute Details

#bodyObject

Response body.


37
38
39
# File 'lib/bettercap/proxy/http/response.rb', line 37

def body
  @body
end

#charsetObject (readonly)

Response charset, default to UTF-8.


29
30
31
# File 'lib/bettercap/proxy/http/response.rb', line 29

def charset
  @charset
end

#chunkedObject (readonly)

True if this is a chunked encoded response, otherwise false.


33
34
35
# File 'lib/bettercap/proxy/http/response.rb', line 33

def chunked
  @chunked
end

#codeObject

Response status code.


23
24
25
# File 'lib/bettercap/proxy/http/response.rb', line 23

def code
  @code
end

#content_lengthObject (readonly)

Response content length.


31
32
33
# File 'lib/bettercap/proxy/http/response.rb', line 31

def content_length
  @content_length
end

#content_typeObject (readonly)

Response content type.


27
28
29
# File 'lib/bettercap/proxy/http/response.rb', line 27

def content_type
  @content_type
end

#headersObject

A list of response headers.


35
36
37
# File 'lib/bettercap/proxy/http/response.rb', line 35

def headers
  @headers
end

#statusObject

Response status message


25
26
27
# File 'lib/bettercap/proxy/http/response.rb', line 25

def status
  @status
end

#versionObject

HTTP protocol version


21
22
23
# File 'lib/bettercap/proxy/http/response.rb', line 21

def version
  @version
end

Class Method Details

.from_file(filename, content_type) ⇒ Object

Return a 200 response object reading the file filename with the specified content_type.


41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/bettercap/proxy/http/response.rb', line 41

def self.from_file( filename, content_type )
  r = Response.new
  data = File.read(filename)

  r << "HTTP/1.1 200 OK"
  r << "Connection: close"
  r << "Content-Length: #{data.bytesize}"
  r << "Content-Type: #{content_type}"
  r << "\n"
  r << data

  r
end

.redirect(url, cookies = []) ⇒ Object

Return a 302 response object redirecting to url, setting optional cookies.


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/bettercap/proxy/http/response.rb', line 56

def self.redirect( url, cookies = [] )
  r = Response.new

  r << "HTTP/1.1 302 Moved"
  r << "Location: #{url}"

  cookies.each do |cookie|
    r << "Set-Cookie: #{cookie}"
  end

  r << "Connection: close"
  r << "\n\n"

  r
end

Instance Method Details

#<<(line) ⇒ Object

Parse a single response line.


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/bettercap/proxy/http/response.rb', line 106

def <<(line)
  # we already parsed the heders, collect response body
  if @headers_done
    @body = '' if @body.nil?
    @body << line.force_encoding( @charset )
  else
    chomped = line.chomp
    Logger.debug "  RESPONSE LINE: '#{chomped}'"

    # is this the first line 'HTTP/<VERSION> <CODE> <STATUS>' ?
    if chomped =~ /^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/
      @version = $1
      @code    = $2.to_i
      @status  = $3

    # collect and fix headers
    elsif chomped =~ /^([^:\s]+)\s*:\s*(.+)$/i
      name = $1
      value = $2

      if name == 'Content-Type'
        @content_type = value
        if value =~ /^(.+);\s*charset=(.+)/i
          @content_type = $1
          @charset = $2.chomp
        end
      elsif name == 'Content-Length'
        @content_length = value.to_i
      # check if we have a chunked encoding
      elsif name == 'Transfer-Encoding' and value == 'chunked'
        @chunked = true
        name     = nil
        value    = nil
      end

      unless name.nil? or value.nil?
        if @headers.has_key?(name)
          if @headers[name].is_a?(Array)
            @headers[name] << value
          else
            @headers[name] = [ @headers[name], value ]
          end
        else
          @headers[name] = value
        end
      end
    # last line, we're done with the headers
    elsif chomped.empty?
      @headers_done = true
    end
  end
end

#[](name) ⇒ Object

Return the value of header with name or an empty string.


165
166
167
# File 'lib/bettercap/proxy/http/response.rb', line 165

def [](name)
  ( @headers.has_key?(name) ? @headers[name] : "" )
end

#[]=(name, value) ⇒ Object

If the header with name is found, then a value is assigned to it, otherwise it's created.


171
172
173
174
175
176
177
178
179
180
181
# File 'lib/bettercap/proxy/http/response.rb', line 171

def []=(name, value)
  if @headers.has_key?(name)
    if value.nil?
      @headers.delete(name)
    else
      @headers[name] = value
    end
  elsif !value.nil?
    @headers[name] = value
  end
end

#convert_webrick_response!(response) ⇒ Object

Convert a webrick response to this class.


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/bettercap/proxy/http/response.rb', line 87

def convert_webrick_response!(response)
  self << "HTTP/#{response.http_version} #{response.code} #{response.msg}"
  response.each do |key,value|
    # sometimes webrick joins all 'set-cookie' headers
    # which might cause issues with HSTS bypass.
    if key == 'set-cookie'
      response.get_fields('set-cookie').each do |v|
        self << "Set-Cookie: #{v}"
      end
    else
      self << "#{key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }}: #{value}"
    end
  end
  self << "\n"
  @code = response.code
  @body = response.body || ''
end

#patch_header!(name, search, replace) ⇒ Object

Search for header name and apply a gsub substitution:

value.gsub( +search+, +replace+ )

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/bettercap/proxy/http/response.rb', line 185

def patch_header!( name, search, replace )
  value = self[name]
  unless value.empty?
    patched = []
    if value.is_a?(Array)
      value.each do |v|
        patched << v.gsub( search, replace )
      end
    else
      patched << value.gsub( search, replace )
    end

    self[name] = patched
  end
end

#textual?Boolean

Return true if the response content type is textual, otherwise false.

Returns:

  • (Boolean)

160
161
162
# File 'lib/bettercap/proxy/http/response.rb', line 160

def textual?
  @content_type and ( @content_type =~ /^text\/.+/ or @content_type =~ /^application\/.+/ )
end

#to_sObject

Return a string representation of this response object, patching the Content-Length header if the #body was modified.


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/bettercap/proxy/http/response.rb', line 203

def to_s
  # update content length in case the body was modified.
  if @headers.has_key?('Content-Length')
    @headers['Content-Length'] = @body.nil?? 0 : @body.bytesize
  end

  s = "HTTP/#{@version} #{@code} #{@status}\n"
  @headers.each do |name,value|
    if value.is_a?(Array)
      value.each do |v|
        s << "#{name}: #{v}\n"
      end
    else
      s << "#{name}: #{value}\n"
    end
  end
  s << "\n" + ( @body.nil?? "\n" : @body )
  s
end