rbkb 0.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/History.txt +74 -0
  2. data/README.rdoc +149 -0
  3. data/Rakefile +47 -0
  4. data/bin/b64 +5 -0
  5. data/bin/bgrep +5 -0
  6. data/bin/blit +5 -0
  7. data/bin/c +5 -0
  8. data/bin/crc32 +5 -0
  9. data/bin/d64 +5 -0
  10. data/bin/dedump +5 -0
  11. data/bin/feed +5 -0
  12. data/bin/hexify +5 -0
  13. data/bin/len +5 -0
  14. data/bin/plugsrv +271 -0
  15. data/bin/rex +10 -0
  16. data/bin/rstrings +5 -0
  17. data/bin/slice +5 -0
  18. data/bin/telson +5 -0
  19. data/bin/unhexify +5 -0
  20. data/bin/urldec +5 -0
  21. data/bin/urlenc +5 -0
  22. data/bin/xor +5 -0
  23. data/cli_usage.rdoc +285 -0
  24. data/doctor-bag.jpg +0 -0
  25. data/lib/rbkb.rb +51 -0
  26. data/lib/rbkb/cli.rb +219 -0
  27. data/lib/rbkb/cli/b64.rb +35 -0
  28. data/lib/rbkb/cli/bgrep.rb +86 -0
  29. data/lib/rbkb/cli/blit.rb +89 -0
  30. data/lib/rbkb/cli/chars.rb +24 -0
  31. data/lib/rbkb/cli/crc32.rb +35 -0
  32. data/lib/rbkb/cli/d64.rb +28 -0
  33. data/lib/rbkb/cli/dedump.rb +52 -0
  34. data/lib/rbkb/cli/feed.rb +229 -0
  35. data/lib/rbkb/cli/hexify.rb +65 -0
  36. data/lib/rbkb/cli/len.rb +76 -0
  37. data/lib/rbkb/cli/rstrings.rb +108 -0
  38. data/lib/rbkb/cli/slice.rb +47 -0
  39. data/lib/rbkb/cli/telson.rb +87 -0
  40. data/lib/rbkb/cli/unhexify.rb +50 -0
  41. data/lib/rbkb/cli/urldec.rb +35 -0
  42. data/lib/rbkb/cli/urlenc.rb +35 -0
  43. data/lib/rbkb/cli/xor.rb +43 -0
  44. data/lib/rbkb/extends.rb +725 -0
  45. data/lib/rbkb/http.rb +21 -0
  46. data/lib/rbkb/http/base.rb +172 -0
  47. data/lib/rbkb/http/body.rb +214 -0
  48. data/lib/rbkb/http/common.rb +74 -0
  49. data/lib/rbkb/http/headers.rb +370 -0
  50. data/lib/rbkb/http/parameters.rb +104 -0
  51. data/lib/rbkb/http/request.rb +58 -0
  52. data/lib/rbkb/http/response.rb +86 -0
  53. data/lib/rbkb/plug.rb +9 -0
  54. data/lib/rbkb/plug/blit.rb +222 -0
  55. data/lib/rbkb/plug/cli.rb +83 -0
  56. data/lib/rbkb/plug/feed_import.rb +74 -0
  57. data/lib/rbkb/plug/peer.rb +67 -0
  58. data/lib/rbkb/plug/plug.rb +215 -0
  59. data/lib/rbkb/plug/proxy.rb +26 -0
  60. data/lib/rbkb/plug/unix_domain.rb +75 -0
  61. data/lib_usage.rdoc +176 -0
  62. data/rbkb.gemspec +38 -0
  63. data/spec/rbkb_spec.rb +7 -0
  64. data/spec/spec_helper.rb +16 -0
  65. data/tasks/ann.rake +80 -0
  66. data/tasks/bones.rake +20 -0
  67. data/tasks/gem.rake +201 -0
  68. data/tasks/git.rake +40 -0
  69. data/tasks/notes.rake +27 -0
  70. data/tasks/post_load.rake +34 -0
  71. data/tasks/rdoc.rake +51 -0
  72. data/tasks/rubyforge.rake +55 -0
  73. data/tasks/setup.rb +292 -0
  74. data/tasks/spec.rake +54 -0
  75. data/tasks/svn.rake +47 -0
  76. data/tasks/test.rake +40 -0
  77. data/test/test_cli_b64.rb +35 -0
  78. data/test/test_cli_bgrep.rb +137 -0
  79. data/test/test_cli_blit.rb +11 -0
  80. data/test/test_cli_chars.rb +21 -0
  81. data/test/test_cli_crc32.rb +108 -0
  82. data/test/test_cli_d64.rb +22 -0
  83. data/test/test_cli_dedump.rb +118 -0
  84. data/test/test_cli_feed.rb +11 -0
  85. data/test/test_cli_helper.rb +96 -0
  86. data/test/test_cli_hexify.rb +63 -0
  87. data/test/test_cli_len.rb +96 -0
  88. data/test/test_cli_rstrings.rb +15 -0
  89. data/test/test_cli_slice.rb +73 -0
  90. data/test/test_cli_telson.rb +11 -0
  91. data/test/test_cli_unhexify.rb +43 -0
  92. data/test/test_cli_urldec.rb +50 -0
  93. data/test/test_cli_urlenc.rb +44 -0
  94. data/test/test_cli_xor.rb +71 -0
  95. data/test/test_helper.rb +5 -0
  96. data/test/test_http.rb +27 -0
  97. data/test/test_http_helper.rb +60 -0
  98. data/test/test_http_request.rb +136 -0
  99. data/test/test_http_response.rb +222 -0
  100. data/test/test_rbkb.rb +19 -0
  101. metadata +238 -0
data/lib/rbkb/http.rb ADDED
@@ -0,0 +1,21 @@
1
+
2
+ # ???Why???? would anyone create their own HTTP implementation in ruby with
3
+ # so many options out there? Short answer: Net:HTTP and others just don't cut
4
+ # it in lots of edge cases. I needed something I could control completely.
5
+
6
+ module Rbkb
7
+ module Http
8
+ VERSION = "0.0.3"
9
+ end
10
+ end
11
+
12
+ require 'time' # gives us Time.httpdate parser and output methods
13
+
14
+ require "rbkb/http/common.rb"
15
+ require "rbkb/http/base.rb"
16
+ require "rbkb/http/request.rb"
17
+ require "rbkb/http/response.rb"
18
+ require "rbkb/http/headers.rb"
19
+ require "rbkb/http/body.rb"
20
+ require "rbkb/http/parameters.rb"
21
+
@@ -0,0 +1,172 @@
1
+ module Rbkb::Http
2
+
3
+ # A base class containing some common features for Request and Response
4
+ # objects.
5
+ #
6
+ # Don't use this class directly, it's intended for being overridden
7
+ # from its derived classes or mixins.
8
+ class Base
9
+ include CommonInterface
10
+
11
+ def self.parse(*args)
12
+ new(*args)
13
+ end
14
+
15
+ # Initializes a new Base object
16
+ def initialize(*args)
17
+ _common_init(*args)
18
+ end
19
+
20
+ # This method parses just HTTP message body. Expects body to be split
21
+ # from the headers before-hand.
22
+ def capture_body(bstr)
23
+ self.body ||= default_body_obj
24
+ @body.capture(bstr)
25
+ end
26
+
27
+ # XXX stub
28
+ def first_entity
29
+ @first_entity
30
+ end
31
+
32
+ # XXX stub
33
+ def first_entity=(f)
34
+ @first_entity=(f)
35
+ end
36
+
37
+ # This method parses only HTTP response headers. Expects headers to be
38
+ # split from the body before-hand.
39
+ def capture_headers(hstr)
40
+ self.headers ||= default_headers_obj
41
+
42
+ if @body and not @body.capture_complete?
43
+ return
44
+ elsif @headers.capture_complete?
45
+ self.first_entity, @headers = default_headers_obj.capture_full_headers(hstr)
46
+ else
47
+ @headers.capture(hstr)
48
+ end
49
+ end
50
+
51
+ # This method returns the content length from Headers. This is
52
+ # mostly useful if you are using a BoundBody object for the body.
53
+ #
54
+ # Returns nil if no "Content-Length" is not found.
55
+ #
56
+ # The opts parameter :ignore_content_length affects this method and
57
+ # will cause it always to return nil. This is useful, for example,
58
+ # for the responses to the HTTP HEAD request method, which return
59
+ # a Content-Length without actual content.
60
+ #
61
+ def content_length(hdrs=@headers)
62
+ raise "headers is nil?" if not hdrs
63
+ if( (not @opts[:ignore_content_length]) and
64
+ hdrs.get_header_value("Content-Length").to_s =~ /^(\d+)$/ )
65
+
66
+ $1.to_i
67
+ end
68
+ end
69
+
70
+ def attach_new_header(hdr_obj=nil)
71
+ self.headers = hdr_obj
72
+ return hdr_obj
73
+ end
74
+
75
+ def attach_new_body(body_obj=nil)
76
+ self.body = body_obj
77
+ return body_obj
78
+ end
79
+
80
+ # XXX doc override!
81
+ def default_headers_obj(*args)
82
+ Header.new(*args)
83
+ end
84
+
85
+ # XXX doc override!
86
+ def default_body_obj(*args)
87
+ Body.new(*args)
88
+ end
89
+
90
+ # This method will non-destructively reset the capture state on this
91
+ # object and all child entities. Note, however, If child entities are not
92
+ # defined, it may instantiate new ones.
93
+ # See also: capture_complete?, reset_capture!
94
+ def reset_capture
95
+ if @headers
96
+ @headers.reset_capture if not @headers.capture_complete?
97
+ else
98
+ attach_new_header()
99
+ end
100
+
101
+ if @body
102
+ @body.reset_capture if not @body.capture_complete?
103
+ else
104
+ attach_new_body()
105
+ end
106
+ @capture_state = nil
107
+ self
108
+ end
109
+
110
+ # This method will destructively reset the capture state on this object.
111
+ # It does so by initializing fresh child entities and discarding the old
112
+ # ones. See also: capture_complete?, reset_capture
113
+ def reset_capture!
114
+ attach_new_header()
115
+ attach_new_body()
116
+ @capture_state = nil
117
+ self
118
+ end
119
+
120
+ # Indicates whether this object is ready to capture fresh data, or is
121
+ # waiting for additional data or a reset from a previous incomplete or
122
+ # otherwise broken capture. See also: reset_capture, reset_capture!
123
+ def capture_complete?
124
+ if( (@headers and not @headers.capture_complete?) or
125
+ (@body and not @body.capture_complete?) )
126
+ return false
127
+ else
128
+ true
129
+ end
130
+ end
131
+
132
+ attr_reader :body, :headers
133
+
134
+ # This accessor will attempt to always do the "right thing" while
135
+ # setting this object's body entity.
136
+ #
137
+ # See also: default_body_obj
138
+ def body=(b)
139
+ if @body
140
+ @body.data = b
141
+ elsif b.kind_of? Body
142
+ @body = b.dup
143
+ @body.opts = b.opts
144
+ else
145
+ @body = default_body_obj(b)
146
+ end
147
+ @body.base = self
148
+ return @body
149
+ end
150
+
151
+ # This accessor will attempt to always do the "right thing" while
152
+ # setting this object's headers entity.
153
+ #
154
+ # See also: default_headers_obj
155
+ def headers=(h)
156
+ if @headers
157
+ @headers.data = h
158
+ elsif h.kind_of? Headers
159
+ @headers = h.dup
160
+ @headers.opts = h.opts
161
+ else
162
+ @headers = default_headers_obj(h)
163
+ end
164
+ @headers.base = self
165
+ return @body
166
+ end
167
+
168
+ end
169
+
170
+
171
+ end
172
+
@@ -0,0 +1,214 @@
1
+ require 'stringio'
2
+
3
+ module Rbkb::Http
4
+ class Body < String
5
+ include CommonInterface
6
+
7
+ def self.parse(str)
8
+ new().capture(str)
9
+ end
10
+
11
+ attr_reader :expect_length
12
+
13
+ def initialize(str=nil, opts=nil)
14
+ self.opts = opts
15
+ if Body === str
16
+ self.replace(str)
17
+ @opts = str.opts.merge(@opts)
18
+ elsif String === str
19
+ super(str)
20
+ else
21
+ super()
22
+ end
23
+
24
+ yield(self) if block_given?
25
+ end
26
+
27
+ # The capture method is used when parsing HTTP requests/responses.
28
+ # This can and probably should be overridden in derived classes.
29
+ def capture(str)
30
+ yield(str) if block_given?
31
+ self.data=(str)
32
+ end
33
+
34
+ # The to_raw method is used when writing HTTP requests/responses.
35
+ # This can and probably should be overridden in derived classes.
36
+ def to_raw
37
+ (block_given?) ? yield(self.data) : self.data
38
+ end
39
+
40
+ attr_reader :base
41
+
42
+ def base=(b)
43
+ if b.nil? or b.is_a? Base
44
+ @base = b
45
+ else
46
+ raise "base must be a Response or Request object or nil"
47
+ end
48
+ end
49
+
50
+ def data
51
+ self
52
+ end
53
+
54
+ # Sets internal raw string data without any HTTP decoration.
55
+ def data=(str)
56
+ self.replace(str.to_s)
57
+ end
58
+
59
+ # Returns the content length from the HTTP base object if
60
+ # there is one and content-length is available.
61
+ def get_content_length
62
+ @base.content_length if @base
63
+ end
64
+
65
+ # This method will non-destructively reset the capture state on this object.
66
+ # It is non-destructive in that it will not affect existing captured data
67
+ # if present.
68
+ def reset_capture
69
+ @expect_length = nil
70
+ @base.reset_capture() if @base and @base.capture_complete?
71
+ end
72
+
73
+ # This method will destructively reset the capture state on this object.
74
+ # This method is destructive in that it will clear any previously captured
75
+ # data.
76
+ def reset_capture!
77
+ reset_capture()
78
+ self.data=""
79
+ end
80
+
81
+ def capture_complete?
82
+ not @expect_length
83
+ end
84
+ end
85
+
86
+
87
+ # BoundBody is designed for handling an HTTP body when using the usual
88
+ # "Content-Length: NNN" HTTP header.
89
+ class BoundBody < Body
90
+
91
+ # This method may throw :expect_length with one of the following values
92
+ # to indicate certain content-length conditions:
93
+ #
94
+ # > 0 : Got incomplete data in this capture. The object expects
95
+ # capture to be called again with more body data.
96
+ #
97
+ # < 0 : Got more data than expected, the caller should truncate and
98
+ # handle the extra data in some way. Note: Calling capture again
99
+ # on this instance will start a fresh body capture.
100
+ #
101
+ # Caller can also detect the above conditions by checking the expect_length
102
+ # attribute but should still be prepared handle the throw().
103
+ #
104
+ # 0/nil: Got exactly what was expected. Caller can proceed with fresh
105
+ # captures on this or other Body objects.
106
+ #
107
+ # See also reset_capture and reset_capture!
108
+ def capture(str)
109
+ raise "arg 0 must be a string" unless String === str
110
+
111
+ # Start fresh unless we're expecting more data
112
+ self.data="" unless @expect_length and @expect_length > 0
113
+
114
+ if not clen=get_content_length()
115
+ raise "content-length is unknown. aborting capture"
116
+ else
117
+ @expect_length = clen - (self.size + str.size)
118
+ self << str[0, clen - self.size]
119
+ if @expect_length > 0
120
+ throw(:expect_length, @expect_length)
121
+ elsif @expect_length < 0
122
+ throw(:expect_length, @expect_length)
123
+ else
124
+ reset_capture()
125
+ end
126
+ end
127
+ return self
128
+ end
129
+
130
+ def to_raw(*args)
131
+ if @base
132
+ @base.headers.set_header("Content-Length", self.size)
133
+ end
134
+ super(*args)
135
+ end
136
+ end
137
+
138
+
139
+ # ChunkedBody is designed for handling an HTTP body when using a
140
+ # "Transfer-Encoding: chunked" HTTP header.
141
+ class ChunkedBody < Body
142
+ DEFAULT_CHUNK_SIZE = 2048
143
+
144
+ # Throws :expect_length with 'true' when given incomplete data and expects
145
+ # to be called again with more body data to parse.
146
+ #
147
+ # The caller can also detect this condition by checking the expect_length
148
+ # attribute but must still handle the throw().
149
+ #
150
+ # See also reset_capture and reset_capture!
151
+ def capture(str)
152
+ # chunked encoding is gross...
153
+ if @expect_length
154
+ sio = StringIO.new(@last_chunk.to_s + str)
155
+ else
156
+ sio = StringIO.new(str)
157
+ self.data=""
158
+ end
159
+ @last_chunk = nil
160
+
161
+ @expect_length = true
162
+ while not sio.eof?
163
+ unless m=/^([a-fA-F0-9]+)\s*(;[[:print:]\s]*)?\r?\n$/.match(line=sio.readline)
164
+ raise "invalid chunk at #{line.chomp.inspect}"
165
+ end
166
+ if (chunksz = m[1].hex) == 0
167
+ @expect_length = false
168
+ # XXX ignore Trailer headers
169
+ break
170
+ end
171
+
172
+ if ( (not sio.eof?) and
173
+ (chunk=sio.read(chunksz)) and
174
+ chunk.size == chunksz and
175
+ (not sio.eof?) and (extra = sio.readline) and
176
+ (not sio.eof?) and (extra << sio.readline)
177
+ )
178
+ if extra =~ /^\r?\n\r?\n$/
179
+ yield(chunk) if block_given?
180
+ self << chunk
181
+ else
182
+ raise "expected CRLF"
183
+ end
184
+ else
185
+ @last_chunk = line + chunk.to_s + extra.to_s
186
+ break
187
+ end
188
+ end
189
+ throw(:expect_length, @expect_length) if @expect_length
190
+ return self
191
+ end
192
+
193
+
194
+ def to_raw(csz=nil)
195
+ csz ||= (@opts[:output_chunk_size] || DEFAULT_CHUNK_SIZE)
196
+ unless csz.kind_of? Integer and csz > 0
197
+ raise "chunk size must be an integer >= 1"
198
+ end
199
+
200
+ out=[]
201
+ i=0
202
+ while i <= self.size
203
+ chunk = self[i, csz]
204
+ out << "#{chunk.size.to_s(16)}\r\n#{chunk}\r\n\r\n"
205
+ yield(self, out.last) if block_given?
206
+ i+=csz
207
+ end
208
+ out << "0\r\n"
209
+ yield(self, out.last) if block_given?
210
+ return out.join
211
+ end
212
+ end
213
+ end
214
+
@@ -0,0 +1,74 @@
1
+ module Rbkb::Http
2
+ DEFAULT_HTTP_VERSION = "HTTP/1.1"
3
+
4
+ module CommonInterface
5
+ # This provides a common method for use in 'initialize' to slurp in
6
+ # opts parameters and optionally capture a raw blob. This method also
7
+ # accepts a block to which it yields 'self'
8
+ def _common_init(raw=nil, opts=nil)
9
+ self.opts = opts
10
+ yield self if block_given?
11
+ capture(raw) if raw
12
+ return self
13
+ end
14
+
15
+ # Implements a common interface for an opts hash which is stored internally
16
+ # as the class variable @opts.
17
+ #
18
+ # The opts hash is designed to contain various named values for
19
+ # configuration, etc. The values and names are determined entirely
20
+ # by the class that uses it.
21
+ def opts
22
+ @opts
23
+ end
24
+
25
+ # Implements a common interface for setting a new opts hash containing
26
+ # various named values for configuration, etc. This also performs a
27
+ # minimal sanity check to ensure the object is a Hash.
28
+ def opts=(o=nil)
29
+ raise "opts must be a hash" unless (o ||= {}).is_a? Hash
30
+ @opts = o
31
+ end
32
+ end
33
+
34
+
35
+ # A generic cheat for an Array of named value pairs to pretend to
36
+ # be like Hash when using [] and []=
37
+ class NamedValueArray < Array
38
+
39
+ # Act like a hash with named values. Return the named value if a string
40
+ # or Symbol is supplied as the index argument.
41
+ #
42
+ # Note, this doesn't do any magic with String / Symbol conversion.
43
+ def [](*args)
44
+ if args.size == 1 and (String === args[0] or Symbol === args[0])
45
+ if h=find {|x| x[0] == args[0]}
46
+ return h[1]
47
+ end
48
+ else
49
+ super(*args)
50
+ end
51
+ end
52
+
53
+ # Act like a hash with named values. Set the named value if a String
54
+ # or Symbol is supplied as the index argument.
55
+ #
56
+ # Note, this doesn't do any magic with String / Symbol conversion.
57
+ def []=(*args)
58
+ if args.size > 1 and (String === args[0] or Symbol === args[0])
59
+ if h=find {|x| x[0] == args[0]}
60
+ h[1] = args[1]
61
+ else
62
+ self << args[0,2]
63
+ end
64
+ else
65
+ super(*args)
66
+ end
67
+ end
68
+
69
+ def delete_key(key)
70
+ delete_if {|x| x[0] == key }
71
+ end
72
+ end
73
+
74
+ end