nyara 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/example/design.rb +62 -0
  3. data/example/fib.rb +15 -0
  4. data/example/hello.rb +5 -0
  5. data/example/stream.rb +10 -0
  6. data/ext/accept.c +133 -0
  7. data/ext/event.c +89 -0
  8. data/ext/extconf.rb +34 -0
  9. data/ext/hashes.c +130 -0
  10. data/ext/http-parser/AUTHORS +41 -0
  11. data/ext/http-parser/CONTRIBUTIONS +4 -0
  12. data/ext/http-parser/LICENSE-MIT +23 -0
  13. data/ext/http-parser/contrib/parsertrace.c +156 -0
  14. data/ext/http-parser/contrib/url_parser.c +44 -0
  15. data/ext/http-parser/http_parser.c +2175 -0
  16. data/ext/http-parser/http_parser.h +304 -0
  17. data/ext/http-parser/test.c +3425 -0
  18. data/ext/http_parser.c +1 -0
  19. data/ext/inc/epoll.h +60 -0
  20. data/ext/inc/kqueue.h +77 -0
  21. data/ext/inc/status_codes.inc +64 -0
  22. data/ext/inc/str_intern.h +66 -0
  23. data/ext/inc/version.inc +1 -0
  24. data/ext/mime.c +107 -0
  25. data/ext/multipart-parser-c/README.md +18 -0
  26. data/ext/multipart-parser-c/multipart_parser.c +309 -0
  27. data/ext/multipart-parser-c/multipart_parser.h +48 -0
  28. data/ext/multipart_parser.c +1 -0
  29. data/ext/nyara.c +56 -0
  30. data/ext/nyara.h +59 -0
  31. data/ext/request.c +474 -0
  32. data/ext/route.cc +325 -0
  33. data/ext/url_encoded.c +304 -0
  34. data/hello.rb +5 -0
  35. data/lib/nyara/config.rb +64 -0
  36. data/lib/nyara/config_hash.rb +51 -0
  37. data/lib/nyara/controller.rb +336 -0
  38. data/lib/nyara/cookie.rb +31 -0
  39. data/lib/nyara/cpu_counter.rb +65 -0
  40. data/lib/nyara/header_hash.rb +18 -0
  41. data/lib/nyara/mime_types.rb +612 -0
  42. data/lib/nyara/nyara.rb +82 -0
  43. data/lib/nyara/param_hash.rb +5 -0
  44. data/lib/nyara/request.rb +144 -0
  45. data/lib/nyara/route.rb +138 -0
  46. data/lib/nyara/route_entry.rb +43 -0
  47. data/lib/nyara/session.rb +104 -0
  48. data/lib/nyara/view.rb +317 -0
  49. data/lib/nyara.rb +25 -0
  50. data/nyara.gemspec +20 -0
  51. data/rakefile +91 -0
  52. data/readme.md +35 -0
  53. data/spec/ext_mime_match_spec.rb +27 -0
  54. data/spec/ext_parse_accept_value_spec.rb +29 -0
  55. data/spec/ext_parse_spec.rb +138 -0
  56. data/spec/ext_route_spec.rb +70 -0
  57. data/spec/hashes_spec.rb +71 -0
  58. data/spec/path_helper_spec.rb +77 -0
  59. data/spec/request_delegate_spec.rb +67 -0
  60. data/spec/request_spec.rb +56 -0
  61. data/spec/route_entry_spec.rb +12 -0
  62. data/spec/route_spec.rb +84 -0
  63. data/spec/session_spec.rb +66 -0
  64. data/spec/spec_helper.rb +52 -0
  65. data/spec/view_spec.rb +87 -0
  66. data/tools/bench-cookie.rb +22 -0
  67. metadata +111 -0
data/lib/nyara/view.rb ADDED
@@ -0,0 +1,317 @@
1
+ module Nyara
2
+ module Renderable
3
+ end
4
+
5
+ # A support class which provides:
6
+ # - layout / locals for rendering
7
+ # - template search
8
+ # - template default content-type mapping
9
+ # - template precompile
10
+ # - streaming
11
+ #
12
+ # Streaming is implemented in this way: when +Fiber.yield+ is called, we flush +View#out+ and send the data.
13
+ # This adds a bit limitations to the layouts.
14
+ # Consider this case (+friend+ fills into View#out, while +enemy+ doesn't):
15
+ #
16
+ # friend layout { enemy layout { friend page } }
17
+ #
18
+ # Friend layout and friend page shares one buffer, but enemy layout just concats +buffer.join+ before we flush friend layout.
19
+ # So the simple solution is: templates other than stream-friendly ones are not allowed to be a layout.
20
+ #
21
+ # Note on Erubis: to support streaming, Erubis is disabled even loaded.
22
+ class View
23
+ # ext (without dot) => most preferrable content type (e.g. "text/html")
24
+ ENGINE_DEFAULT_CONTENT_TYPES = ParamHash.new
25
+
26
+ # ext (without dot) => stream friendly
27
+ ENGINE_STREAM_FRIENDLY = ParamHash.new
28
+
29
+ class Buffer < Array
30
+ def join
31
+ r = super
32
+ clear
33
+ r
34
+ end
35
+ end
36
+
37
+ class << self
38
+ def init
39
+ @root = Config['views']
40
+ @meth2ext = {} # meth => ext (without dot)
41
+ @meth2sig = {}
42
+ end
43
+ attr_reader :root
44
+
45
+ # +path+ needs extension
46
+ def on_delete path
47
+ meth = path2meth path
48
+ Renderable.class_eval do
49
+ undef meth
50
+ end
51
+
52
+ @meth2ext.delete meth
53
+ @meth2sig.delete meth
54
+ end
55
+
56
+ def on_delete_all
57
+ meths = @meth2sig
58
+ Renderable.class_eval do
59
+ meths.each do |meth, _|
60
+ undef meth
61
+ end
62
+ end
63
+ @meth2sig.clear
64
+ @meth2ext.clear
65
+ end
66
+
67
+ # +path+ needs extension
68
+ # returns dot_ext for further use
69
+ def on_update path
70
+ meth = path2meth path
71
+ return unless @meth2sig[meth] # has not been searched before, see also View.template
72
+
73
+ ext = File.extname(path)[1..-1]
74
+ return unless ext
75
+ src = precompile ext do
76
+ Dir.chdir(@root){ File.read path, encoding: 'utf-8' }
77
+ end
78
+
79
+ if src
80
+ sig = @meth2sig[meth].map{|k| "#{k}: nil" }.join ','
81
+ sig = '_={}' if sig.empty?
82
+ Renderable.class_eval <<-RUBY, path, 1
83
+ def render #{sig}
84
+ #{src}
85
+ end
86
+ alias :#{meth.inspect} render
87
+ RUBY
88
+ else
89
+ t = Dir.chdir @root do
90
+ # todo display template error
91
+ Tilt.new path rescue return
92
+ end
93
+ # partly precompiled
94
+ Renderable.send :define_method, meth do |locals=nil, &p|
95
+ t.render self, locals, &p
96
+ end
97
+ end
98
+
99
+ @meth2ext[meth] = ext
100
+ end
101
+
102
+ # define inline render method and add Content-Type mapping
103
+ def register_engine ext, default_content_type, stream_friendly=false
104
+ # todo figure out fname and line
105
+ meth = engine2meth ext
106
+ file = "file".inspect
107
+ line = 1
108
+
109
+ if stream_friendly
110
+ Renderable.class_eval <<-RUBY, __FILE__, __LINE__ + 1
111
+ def render locals={}
112
+ @_nyara_locals = locals
113
+ src = locals.map{|k, _| "\#{k} = @_nyara_locals[:\#{k}];" }.join
114
+ src << View.precompile(#{ext.inspect}){ @_nyara_view.in }
115
+ instance_eval src, #{file}, #{line}
116
+ end
117
+ alias :#{meth.inspect} render
118
+ RUBY
119
+ ENGINE_STREAM_FRIENDLY[ext] = true
120
+ else
121
+ Renderable.class_eval <<-RUBY, __FILE__, __LINE__ + 1
122
+ def render locals=nil
123
+ Tilt[#{ext.inspect}].new(#{file}, #{line}){ @_nyara_view.in }.render self, locals
124
+ end
125
+ alias :#{meth.inspect} render
126
+ RUBY
127
+ end
128
+ ENGINE_DEFAULT_CONTENT_TYPES[ext] = default_content_type
129
+ end
130
+
131
+ # returns +[meth, ext_without_dot]+
132
+ def template path, locals={}
133
+ if File.extname(path).empty?
134
+ @ext_list ||= Tilt.mappings.keys.delete_if(&:empty?).join ','
135
+ Dir.chdir @root do
136
+ paths = Dir.glob("#{path}.{#@ext_list}")
137
+ if paths.size > 1
138
+ raise ArgumentError, "more than 1 matching views: #{paths.inspect}, add file extension to distinguish them"
139
+ end
140
+ path = paths.first
141
+ end
142
+ end
143
+
144
+ meth = path2meth path
145
+ ext = @meth2ext[meth]
146
+ return [meth, ext] if ext
147
+
148
+ @meth2sig[meth] = locals.keys
149
+ ext = on_update path
150
+ raise "template not found or not valid in Tilt: #{path}" unless ext
151
+ [meth, ext]
152
+ end
153
+
154
+ # private
155
+
156
+ # Block is lazy invoked when it's ok to read the template source.
157
+ def precompile ext
158
+ src_method =\
159
+ case ext
160
+ when 'slim'
161
+ :slim_src
162
+ when 'erb', 'rhtml'
163
+ :erb_src
164
+ when 'haml'
165
+ :haml_src
166
+ end
167
+ return unless src_method
168
+
169
+ send src_method, yield
170
+ end
171
+
172
+ def erb_src template
173
+ @erb_compiler ||= begin
174
+ c = ERB::Compiler.new '<>' # trim mode
175
+ c.pre_cmd = ["_erbout = @_nyara_view.out"]
176
+ c.put_cmd = "_erbout.push" # after newline
177
+ c.insert_cmd = "_erbout.push" # before newline
178
+ c.post_cmd = ["_erbout.join"]
179
+ c
180
+ end
181
+ src, enc = @erb_compiler.compile template
182
+ # todo do sth with enc?
183
+ src
184
+ end
185
+
186
+ def slim_src template
187
+ # todo pretty by env
188
+ t = Slim::Template.new(nil, nil, pretty: false){ template }
189
+ src = t.instance_variable_get :@src
190
+ if src.start_with?('_buf = []')
191
+ src.sub! '_buf = []', '_buf = @_nyara_view.out'
192
+ end
193
+ src
194
+ end
195
+
196
+ def haml_src template
197
+ e = Haml::Engine.new template
198
+ # todo trim mode
199
+ <<-RUBY
200
+ _hamlout = Haml::Buffer.new(nil, encoding: 'utf-8')
201
+ _hamlout.buffer = @_nyara_view.out
202
+ #{e.precompiled}
203
+ _hamlout.buffer.join
204
+ RUBY
205
+ end
206
+
207
+ def path2meth path
208
+ "!!#{path}"
209
+ end
210
+
211
+ def engine2meth engine
212
+ "!:#{engine}"
213
+ end
214
+ end
215
+
216
+ # NOTE this is the list used in View.precompile
217
+ %w[slim erb rhtml haml].each {|e| register_engine e, 'text/html', true }
218
+
219
+ %w[ad adoc asciidoc erubis builder liquid mab markdown mkd md
220
+ textile rdoc radius nokogiri wiki creole mediawiki mw
221
+ ].each {|e| register_engine e, 'text/html' }
222
+
223
+ register_engine 'str', 'text/plain'
224
+ register_engine 'coffee', 'application/javascript'
225
+ register_engine 'yajl', 'application/javascript'
226
+ register_engine 'rcsv', 'application/csv'
227
+ register_engine 'sass', 'text/stylesheet'
228
+ register_engine 'scss', 'text/stylesheet'
229
+ register_engine 'less', 'text/stylesheet'
230
+
231
+ # If view_path not given, find template source in opts
232
+ def initialize instance, view_path, layout, locals, opts
233
+ locals ||= {}
234
+ if view_path
235
+ raise ArgumentError, "unkown options: #{opts.inspect}" unless opts.empty?
236
+ meth, ext = View.template(view_path, locals)
237
+
238
+ unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
239
+ raise ArgumentError, "unkown template engine: #{ext.inspect}"
240
+ end
241
+
242
+ @layouts = [[meth, ext]]
243
+ else
244
+ raise ArgumentError, "too many options, expected only 1: #{opts.inspect}" if opts.size > 1
245
+ ext, template = opts.first
246
+ meth = View.engine2meth ext
247
+
248
+ unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
249
+ raise ArgumentError, "unkown template engine: #{ext.inspect}"
250
+ end
251
+
252
+ @layouts = [meth]
253
+ @in = template
254
+ end
255
+
256
+ unless layout.is_a?(Array)
257
+ layout = layout ? [layout] : []
258
+ end
259
+ layout.each do |l|
260
+ pair = View.template(l)
261
+ # see notes on View
262
+ raise "can not use #{meth} as layout" unless ENGINE_STREAM_FRIENDLY[pair[1]]
263
+ @layouts << pair
264
+ end
265
+
266
+ @locals = locals
267
+ @instance = instance
268
+ @instance.instance_variable_set :@_nyara_view, self
269
+ @out = Buffer.new
270
+ end
271
+ attr_reader :deduced_content_type, :in, :out
272
+
273
+ def render
274
+ @rest_layouts = @layouts.dup
275
+ @instance.send_chunk _render
276
+ Fiber.yield :term_close
277
+ end
278
+
279
+ # :nodoc:
280
+ def _render
281
+ t, _ = @rest_layouts.pop
282
+ if @rest_layouts.empty?
283
+ @instance.send t, @locals
284
+ else
285
+ @instance.send t do
286
+ _render
287
+ end
288
+ end
289
+ end
290
+
291
+ def stream
292
+ @rest_layouts = @layouts.dup
293
+ @fiber = Fiber.new do
294
+ @rest_result = _render
295
+ nil
296
+ end
297
+ self
298
+ end
299
+
300
+ def resume
301
+ r = @fiber.resume
302
+ Fiber.yield r if r
303
+ unless @out.empty?
304
+ @instance.send_chunk @out.join
305
+ @out.clear
306
+ end
307
+ end
308
+
309
+ def end
310
+ while @fiber.alive?
311
+ resume
312
+ end
313
+ @instance.send_chunk @rest_result
314
+ Fiber.yield :term_close
315
+ end
316
+ end
317
+ end
data/lib/nyara.rb ADDED
@@ -0,0 +1,25 @@
1
+ # auto run
2
+ require_relative "nyara/nyara"
3
+
4
+ at_exit do
5
+ Nyara::Route.compile
6
+ Nyara::View.init
7
+ Nyara.start_server
8
+ end
9
+
10
+ module Nyara
11
+ class SimpleController < Controller
12
+ end
13
+ end
14
+
15
+ %w[on tag get post put delete patch options].each do |m|
16
+ eval <<-RUBY
17
+ def #{m} *xs, &blk
18
+ Nyara::SimpleController.#{m} *xs, &blk
19
+ end
20
+ RUBY
21
+ end
22
+
23
+ configure do
24
+ map '/', 'nyara::simple'
25
+ end
data/nyara.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "nyara"
3
+ s.version = "0.0.1.pre"
4
+ s.author = "Zete Lui"
5
+ s.email = "nobody@example.com"
6
+ s.homepage = "https://github.com/luikore/nyara"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "Fast, slim and fuzzy ruby web framework + server"
9
+ s.description = "Fast, slim and fuzzy ruby web framework + server, based on preforked event queue and Fiber. NO rack NOR eventmachine are used."
10
+ s.required_ruby_version = ">=2.0.0"
11
+ s.licenses = ['BSD']
12
+
13
+ s.files = Dir.glob('{rakefile,nyara.gemspec,readme.md,**/*.{rb,h,c,cc,inc}}')
14
+ s.files += Dir.glob('ext/http-parser/{AUTHORS,CONTRIBUTIONS,LICENSE-MIT}')
15
+ s.files += Dir.glob('ext/multipart-parser-c/README.md')
16
+ s.require_paths = ["lib"]
17
+ s.extensions = ["ext/extconf.rb"]
18
+ s.rubygems_version = '2.0.3'
19
+ s.has_rdoc = false
20
+ end
data/rakefile ADDED
@@ -0,0 +1,91 @@
1
+ require "rake"
2
+
3
+ Dir.chdir __dir__
4
+
5
+ status_file = "ext/inc/status_codes.inc"
6
+ version_file = "ext/inc/version.inc"
7
+
8
+ desc "code generate"
9
+ task :gen => [status_file, version_file]
10
+
11
+ desc "generate #{status_file}"
12
+ file status_file do
13
+ puts "generating: #{status_file}"
14
+ require "nokogiri"
15
+ require "open-uri"
16
+ f = File.open status_file, 'w'
17
+ f.puts "#define HTTP_STATUS_CODES(XX)\\"
18
+ Nokogiri::XML(open("http://www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each do |r|
19
+ value = r.css('value').text
20
+ next if value.index '-'
21
+ description = r.css('description').text
22
+ f.puts %Q| XX(#{value}, "#{description}");\\|
23
+ end
24
+ f.puts "// end define"
25
+ f.close
26
+ end
27
+
28
+ desc "generate #{version_file}"
29
+ file version_file => 'nyara.gemspec' do
30
+ puts "generating: #{version_file}"
31
+ lines = File.readlines('nyara.gemspec')
32
+ version = nil
33
+ lines.each do |line|
34
+ if line =~ /s\.version =/
35
+ version = line[/\d+(\.\d+)*(\.pre)?/]
36
+ break
37
+ end
38
+ end
39
+ abort 'version not found' unless version
40
+ File.open version_file, 'w' do |f|
41
+ f.puts %Q{#define NYARA_VERSION "#{version}"}
42
+ end
43
+ end
44
+
45
+ desc "build ext"
46
+ task :build do
47
+ Dir.chdir 'ext' do
48
+ sh 'make'
49
+ end
50
+ end
51
+
52
+ desc "test"
53
+ task :test => :build do
54
+ sh 'rspec', '-c'
55
+ end
56
+
57
+ desc "build and test"
58
+ task :default => :test
59
+
60
+ desc "build and install gem"
61
+ task :gem do
62
+ sh 'rm', '-f', '*.gem'
63
+ sh 'gem', 'build', 'nyara.gemspec'
64
+ gem_package = Dir.glob('*.gem').first
65
+ sh 'gem', 'install', '--no-rdoc', '--no-ri', gem_package
66
+ end
67
+
68
+ desc "clean"
69
+ task :clean do
70
+ sh 'rm', '-f', '*.gem'
71
+ Dir.chdir 'ext' do
72
+ sh 'make', 'clean'
73
+ end
74
+ end
75
+
76
+ # -- utils --
77
+
78
+ desc "collect line stat"
79
+ task :lines do
80
+ c = 0
81
+ Dir.glob('{**/*.rb,ext/*.{c,cc,h}}') do |f|
82
+ c += (File.read(f).count "\n")
83
+ end
84
+ puts "#{c} lines"
85
+ end
86
+
87
+ desc "list Nyara::Ext methods"
88
+ task :list_ext do
89
+ require_relative "lib/nyara/nyara"
90
+ puts Nyara::Ext.methods - Module.methods
91
+ end
data/readme.md ADDED
@@ -0,0 +1,35 @@
1
+ git submodule update --recursive
2
+ rake gen
3
+ rake gem
4
+
5
+ # Why fast
6
+
7
+ ## Solid http parsers written in C
8
+
9
+ These evented parsers are used:
10
+
11
+ - http_parser with chunked encoding support
12
+ - multipart request parser
13
+
14
+ And these are implemented in addition:
15
+
16
+ - `Accept-*` parser
17
+ - mime type parser and content type matcher
18
+ - url-encoded parser for parsing path / query / request body
19
+
20
+ ## Fast first-line routing and request class
21
+
22
+ ## Thin evented IO layer built for *nix and Fiber
23
+
24
+ # How fast
25
+
26
+ Speed is feature, there are specs on:
27
+
28
+ - Accept-* parse vs rack
29
+ - MIME parse vs rack
30
+ - query parse vs rack
31
+ - query parse vs houdini
32
+ - layout engine vs tilt
33
+ - evented IO vs eventmachine
34
+ - helloworld vs sinatra
35
+ - pseudo real app speed vs jruby sinatra threaded mode
@@ -0,0 +1,27 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe Ext, '.mime_match' do
5
+ it "#mime_match_seg" do
6
+ a = Ext.mime_match_seg 'text', 'text', 'html'
7
+ assert_equal true, a
8
+
9
+ a = Ext.mime_match_seg 'text/html', 'text', 'stylesheet'
10
+ assert_equal false, a
11
+
12
+ a = Ext.mime_match_seg '*', 'text', 'html'
13
+ assert_equal true, a
14
+ end
15
+
16
+ it "#mime_match works with wildcards" do
17
+ a = Ext.mime_match %w'*', [%w'text html html']
18
+ assert_equal 'html', a
19
+
20
+ a = Ext.mime_match %w'application/javascript text/*', [%w'some text txt', %w'text html html']
21
+ assert_equal 'html', a
22
+
23
+ a = Ext.mime_match %w'text/*', [%w'some text txt']
24
+ assert_nil a
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe Ext, ".parse_accept_value" do
5
+ it 'works' do
6
+ a = Ext.parse_accept_value ''
7
+ assert_equal [], a
8
+
9
+ a = Ext.parse_accept_value "text/plain; q=0.5, text/html,text/x-dvi; q=3.8, text/x-c"
10
+ assert_equal %w[text/html text/x-dvi text/x-c text/plain], a
11
+ end
12
+
13
+ it "ignores q <= 0" do
14
+ a = Ext.parse_accept_value "text/plain; q=0.0, text/html"
15
+ assert_equal(%w'text/html', a)
16
+
17
+ a = Ext.parse_accept_value "*, text/plain; q=-3"
18
+ assert_equal(%w'*', a)
19
+
20
+ a = Ext.parse_accept_value "text/plain; q=0, text/*"
21
+ assert_equal(%w'text/*', a)
22
+ end
23
+
24
+ it ".parse_accept_value should be robust" do
25
+ a = Ext.parse_accept_value 'q=0.1, text/html'
26
+ assert_equal 'text/html', a[1]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,138 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe Ext, "parse" do
5
+ # note: this method is only used in C code
6
+ context "#parse_url_encoded_seg" do
7
+ [false, true].each do |nested|
8
+ context (nested ? 'nested mode' : 'flat mode') do
9
+ it "normal parse" do
10
+ assert_equal({'a' => 'b'}, parse('a=b', nested))
11
+ end
12
+
13
+ it "param seg end with '='" do
14
+ assert_equal({'a' => ''}, parse('a=', nested))
15
+ end
16
+
17
+ it "param seg begin with '='" do
18
+ assert_equal({'' => 'b'}, parse('=b', nested))
19
+ end
20
+
21
+ it "param seg without value" do
22
+ assert_equal({'a' => ''}, parse('a', nested))
23
+ end
24
+
25
+ it "raises error" do
26
+ assert_raise ArgumentError do
27
+ parse 'a=&b'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ context "nested key" do
34
+ it "parses nested key" do
35
+ res = {"a"=>{"b"=>[[{"c"=>"1"}]]}}
36
+ assert_equal res, Ext.parse_url_encoded_seg({}, "a[b][][][c]=1", true)
37
+ end
38
+
39
+ it 'allows "[]" as input' do
40
+ res = {""=>[""]}
41
+ assert_equal res, Ext.parse_url_encoded_seg({}, "[]", true)
42
+ end
43
+
44
+ it 'ignores empty input' do
45
+ res = {}
46
+ assert_equal res, Ext.parse_url_encoded_seg({}, "", true)
47
+ end
48
+
49
+ it "content hash is ParamHash" do
50
+ h = ParamHash.new
51
+ assert_equal ParamHash, Ext.parse_url_encoded_seg(h, "a[b]=c", true)[:a].class
52
+ end
53
+ end
54
+
55
+ def parse str, nested
56
+ h = {}
57
+ Ext.parse_url_encoded_seg h, str, nested
58
+ end
59
+ end
60
+
61
+ context "#parse_path" do
62
+ before :each do
63
+ @output = ''
64
+ end
65
+
66
+ it "parses" do
67
+ i = '/%23+%24'
68
+ assert_equal i.bytesize, parse(i)
69
+ assert_equal "/\x23 \x24", @output
70
+ end
71
+
72
+ it "truncates ? after %" do
73
+ i = '/hello%f3%?world'
74
+ len = parse i
75
+ assert_equal '/hello%f3%?'.bytesize, len
76
+ assert_equal "/hello\xf3%", @output
77
+ end
78
+
79
+ it "truncates ? after begin" do
80
+ i = '?a'
81
+ len = parse i
82
+ assert_equal 1, len
83
+ assert_equal '', @output
84
+ end
85
+
86
+ it "truncates ? before end" do
87
+ i = 'a?'
88
+ len = parse i
89
+ assert_equal 2, len
90
+ assert_equal 'a', @output
91
+ end
92
+
93
+ it "truncates ? after unescaped char" do
94
+ i = 'a?a'
95
+ len = parse i
96
+ assert_equal 2, len
97
+ assert_equal 'a', @output
98
+ end
99
+
100
+ it "truncates ? after escaped char" do
101
+ i = '%40?'
102
+ len = parse i
103
+ assert_equal 4, len
104
+ assert_equal "\x40", @output
105
+ end
106
+
107
+ def parse input
108
+ Ext.parse_path @output, input
109
+ end
110
+ end
111
+
112
+ context ".parse_cookie" do
113
+ it "parses complex cookie" do
114
+ history = CGI.escape '历史'
115
+ cookie = "pgv_pvi; pgv_si= ; pgv_pvi=som; sid=1d6c75f0 ; PLHistory=<#{history}>;"
116
+ h = Ext.parse_cookie ParamHash.new, cookie
117
+ assert_equal '1d6c75f0', h['sid']
118
+ assert_equal '', h['pgv_si']
119
+ assert_equal '', h['pgv_pvi'] # left orverrides right
120
+ assert_equal '<历史>', h['PLHistory']
121
+ end
122
+
123
+ it "parses empty cookie" do
124
+ cookie = ''
125
+ h = Ext.parse_cookie ParamHash.new, cookie
126
+ assert_empty h
127
+ end
128
+ end
129
+
130
+ context ".parse_param" do
131
+ it "parses param with non-utf-8 chars" do
132
+ bad_s = CGI.escape "\xE2"
133
+ h = Ext.parse_param ParamHash.new, bad_s
134
+ assert_equal "", h["\xE2"]
135
+ end
136
+ end
137
+ end
138
+ end