immunio 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +234 -0
  3. data/README.md +147 -0
  4. data/bin/immunio +5 -0
  5. data/lib/immunio.rb +29 -0
  6. data/lib/immunio/agent.rb +260 -0
  7. data/lib/immunio/authentication.rb +96 -0
  8. data/lib/immunio/blocked_app.rb +38 -0
  9. data/lib/immunio/channel.rb +432 -0
  10. data/lib/immunio/cli.rb +39 -0
  11. data/lib/immunio/context.rb +114 -0
  12. data/lib/immunio/errors.rb +43 -0
  13. data/lib/immunio/immunio_ca.crt +45 -0
  14. data/lib/immunio/logger.rb +87 -0
  15. data/lib/immunio/plugins/action_dispatch.rb +45 -0
  16. data/lib/immunio/plugins/action_view.rb +431 -0
  17. data/lib/immunio/plugins/active_record.rb +707 -0
  18. data/lib/immunio/plugins/active_record_relation.rb +370 -0
  19. data/lib/immunio/plugins/authlogic.rb +80 -0
  20. data/lib/immunio/plugins/csrf.rb +24 -0
  21. data/lib/immunio/plugins/devise.rb +40 -0
  22. data/lib/immunio/plugins/environment_reporter.rb +69 -0
  23. data/lib/immunio/plugins/eval.rb +51 -0
  24. data/lib/immunio/plugins/exception_handler.rb +55 -0
  25. data/lib/immunio/plugins/gems_tracker.rb +5 -0
  26. data/lib/immunio/plugins/haml.rb +36 -0
  27. data/lib/immunio/plugins/http_finisher.rb +50 -0
  28. data/lib/immunio/plugins/http_tracker.rb +203 -0
  29. data/lib/immunio/plugins/io.rb +96 -0
  30. data/lib/immunio/plugins/redirect.rb +42 -0
  31. data/lib/immunio/plugins/warden.rb +66 -0
  32. data/lib/immunio/processor.rb +234 -0
  33. data/lib/immunio/rails.rb +26 -0
  34. data/lib/immunio/request.rb +139 -0
  35. data/lib/immunio/rufus_lua_ext/ref.rb +27 -0
  36. data/lib/immunio/rufus_lua_ext/state.rb +157 -0
  37. data/lib/immunio/rufus_lua_ext/table.rb +137 -0
  38. data/lib/immunio/rufus_lua_ext/utils.rb +13 -0
  39. data/lib/immunio/version.rb +5 -0
  40. data/lib/immunio/vm.rb +291 -0
  41. data/lua-hooks/ext/all.c +78 -0
  42. data/lua-hooks/ext/bitop/README +22 -0
  43. data/lua-hooks/ext/bitop/bit.c +189 -0
  44. data/lua-hooks/ext/extconf.rb +38 -0
  45. data/lua-hooks/ext/libinjection/COPYING +37 -0
  46. data/lua-hooks/ext/libinjection/libinjection.h +65 -0
  47. data/lua-hooks/ext/libinjection/libinjection_html5.c +847 -0
  48. data/lua-hooks/ext/libinjection/libinjection_html5.h +54 -0
  49. data/lua-hooks/ext/libinjection/libinjection_sqli.c +2301 -0
  50. data/lua-hooks/ext/libinjection/libinjection_sqli.h +295 -0
  51. data/lua-hooks/ext/libinjection/libinjection_sqli_data.h +9349 -0
  52. data/lua-hooks/ext/libinjection/libinjection_xss.c +531 -0
  53. data/lua-hooks/ext/libinjection/libinjection_xss.h +21 -0
  54. data/lua-hooks/ext/libinjection/lualib.c +109 -0
  55. data/lua-hooks/ext/lpeg/HISTORY +90 -0
  56. data/lua-hooks/ext/lpeg/lpcap.c +537 -0
  57. data/lua-hooks/ext/lpeg/lpcap.h +43 -0
  58. data/lua-hooks/ext/lpeg/lpcode.c +986 -0
  59. data/lua-hooks/ext/lpeg/lpcode.h +34 -0
  60. data/lua-hooks/ext/lpeg/lpeg-128.gif +0 -0
  61. data/lua-hooks/ext/lpeg/lpeg.html +1429 -0
  62. data/lua-hooks/ext/lpeg/lpprint.c +244 -0
  63. data/lua-hooks/ext/lpeg/lpprint.h +35 -0
  64. data/lua-hooks/ext/lpeg/lptree.c +1238 -0
  65. data/lua-hooks/ext/lpeg/lptree.h +77 -0
  66. data/lua-hooks/ext/lpeg/lptypes.h +149 -0
  67. data/lua-hooks/ext/lpeg/lpvm.c +355 -0
  68. data/lua-hooks/ext/lpeg/lpvm.h +58 -0
  69. data/lua-hooks/ext/lpeg/makefile +55 -0
  70. data/lua-hooks/ext/lpeg/re.html +498 -0
  71. data/lua-hooks/ext/lpeg/test.lua +1409 -0
  72. data/lua-hooks/ext/lua-cmsgpack/CMakeLists.txt +45 -0
  73. data/lua-hooks/ext/lua-cmsgpack/README.md +115 -0
  74. data/lua-hooks/ext/lua-cmsgpack/lua_cmsgpack.c +957 -0
  75. data/lua-hooks/ext/lua-cmsgpack/test.lua +570 -0
  76. data/lua-hooks/ext/lua-snapshot/LICENSE +7 -0
  77. data/lua-hooks/ext/lua-snapshot/Makefile +12 -0
  78. data/lua-hooks/ext/lua-snapshot/README.md +18 -0
  79. data/lua-hooks/ext/lua-snapshot/dump.lua +15 -0
  80. data/lua-hooks/ext/lua-snapshot/snapshot.c +455 -0
  81. data/lua-hooks/ext/lua/COPYRIGHT +34 -0
  82. data/lua-hooks/ext/lua/lapi.c +1087 -0
  83. data/lua-hooks/ext/lua/lapi.h +16 -0
  84. data/lua-hooks/ext/lua/lauxlib.c +652 -0
  85. data/lua-hooks/ext/lua/lauxlib.h +174 -0
  86. data/lua-hooks/ext/lua/lbaselib.c +659 -0
  87. data/lua-hooks/ext/lua/lcode.c +831 -0
  88. data/lua-hooks/ext/lua/lcode.h +76 -0
  89. data/lua-hooks/ext/lua/ldblib.c +398 -0
  90. data/lua-hooks/ext/lua/ldebug.c +638 -0
  91. data/lua-hooks/ext/lua/ldebug.h +33 -0
  92. data/lua-hooks/ext/lua/ldo.c +519 -0
  93. data/lua-hooks/ext/lua/ldo.h +57 -0
  94. data/lua-hooks/ext/lua/ldump.c +164 -0
  95. data/lua-hooks/ext/lua/lfunc.c +174 -0
  96. data/lua-hooks/ext/lua/lfunc.h +34 -0
  97. data/lua-hooks/ext/lua/lgc.c +710 -0
  98. data/lua-hooks/ext/lua/lgc.h +110 -0
  99. data/lua-hooks/ext/lua/linit.c +38 -0
  100. data/lua-hooks/ext/lua/liolib.c +556 -0
  101. data/lua-hooks/ext/lua/llex.c +463 -0
  102. data/lua-hooks/ext/lua/llex.h +81 -0
  103. data/lua-hooks/ext/lua/llimits.h +128 -0
  104. data/lua-hooks/ext/lua/lmathlib.c +263 -0
  105. data/lua-hooks/ext/lua/lmem.c +86 -0
  106. data/lua-hooks/ext/lua/lmem.h +49 -0
  107. data/lua-hooks/ext/lua/loadlib.c +705 -0
  108. data/lua-hooks/ext/lua/loadlib_rel.c +760 -0
  109. data/lua-hooks/ext/lua/lobject.c +214 -0
  110. data/lua-hooks/ext/lua/lobject.h +381 -0
  111. data/lua-hooks/ext/lua/lopcodes.c +102 -0
  112. data/lua-hooks/ext/lua/lopcodes.h +268 -0
  113. data/lua-hooks/ext/lua/loslib.c +243 -0
  114. data/lua-hooks/ext/lua/lparser.c +1339 -0
  115. data/lua-hooks/ext/lua/lparser.h +82 -0
  116. data/lua-hooks/ext/lua/lstate.c +214 -0
  117. data/lua-hooks/ext/lua/lstate.h +169 -0
  118. data/lua-hooks/ext/lua/lstring.c +111 -0
  119. data/lua-hooks/ext/lua/lstring.h +31 -0
  120. data/lua-hooks/ext/lua/lstrlib.c +871 -0
  121. data/lua-hooks/ext/lua/ltable.c +588 -0
  122. data/lua-hooks/ext/lua/ltable.h +40 -0
  123. data/lua-hooks/ext/lua/ltablib.c +287 -0
  124. data/lua-hooks/ext/lua/ltm.c +75 -0
  125. data/lua-hooks/ext/lua/ltm.h +54 -0
  126. data/lua-hooks/ext/lua/lua.c +392 -0
  127. data/lua-hooks/ext/lua/lua.def +131 -0
  128. data/lua-hooks/ext/lua/lua.h +388 -0
  129. data/lua-hooks/ext/lua/lua.rc +28 -0
  130. data/lua-hooks/ext/lua/lua_dll.rc +26 -0
  131. data/lua-hooks/ext/lua/luac.c +200 -0
  132. data/lua-hooks/ext/lua/luac.rc +1 -0
  133. data/lua-hooks/ext/lua/luaconf.h +763 -0
  134. data/lua-hooks/ext/lua/luaconf.h.in +724 -0
  135. data/lua-hooks/ext/lua/luaconf.h.orig +763 -0
  136. data/lua-hooks/ext/lua/lualib.h +53 -0
  137. data/lua-hooks/ext/lua/lundump.c +227 -0
  138. data/lua-hooks/ext/lua/lundump.h +36 -0
  139. data/lua-hooks/ext/lua/lvm.c +767 -0
  140. data/lua-hooks/ext/lua/lvm.h +36 -0
  141. data/lua-hooks/ext/lua/lzio.c +82 -0
  142. data/lua-hooks/ext/lua/lzio.h +67 -0
  143. data/lua-hooks/ext/lua/print.c +227 -0
  144. data/lua-hooks/ext/luautf8/README.md +152 -0
  145. data/lua-hooks/ext/luautf8/lutf8lib.c +1274 -0
  146. data/lua-hooks/ext/luautf8/unidata.h +3064 -0
  147. data/lua-hooks/lib/boot.lua +254 -0
  148. data/lua-hooks/lib/encode.lua +4 -0
  149. data/lua-hooks/lib/lexers/LICENSE +21 -0
  150. data/lua-hooks/lib/lexers/bash.lua +134 -0
  151. data/lua-hooks/lib/lexers/bash_dqstr.lua +62 -0
  152. data/lua-hooks/lib/lexers/css.lua +216 -0
  153. data/lua-hooks/lib/lexers/html.lua +106 -0
  154. data/lua-hooks/lib/lexers/javascript.lua +68 -0
  155. data/lua-hooks/lib/lexers/lexer.lua +1575 -0
  156. data/lua-hooks/lib/lexers/markers.lua +33 -0
  157. metadata +308 -0
@@ -0,0 +1,139 @@
1
+ require_relative "vm"
2
+
3
+ module Immunio
4
+ class Request
5
+ # Data sent to the server
6
+ attr_accessor :data, :vm
7
+
8
+ # We keep the VM and hook handlers in the request to ensure all hooks for this request
9
+ # use the same handlers.
10
+ attr_accessor :vm
11
+
12
+ def initialize(env)
13
+ @env = env
14
+ @data = {
15
+ "type" => "engine.http_request",
16
+ "request_id" => id,
17
+ "start_time" => Time.now.utc.iso8601(6),
18
+ }
19
+ @timings = Hash.new { |h, name| h[name] = { count: 0, total_duration: 0 } }
20
+ @paused_times = {}
21
+ @start_time = Time.now
22
+
23
+ Immunio.logger.debug "Created new Request #{id} with data #{@data}"
24
+ end
25
+
26
+ def id
27
+ @env["action_dispatch.request_id"] || internal_id
28
+ end
29
+
30
+ # Time the execution of a block and stores it in the request's `timings`.
31
+ def time(type, name)
32
+ Immunio.logger.debug { "Starting request time for #{type} #{name}" }
33
+ start = Time.now
34
+ key = {type: type, name: name}
35
+ nested = false
36
+ if @paused_times[key]
37
+ nested = true
38
+ else
39
+ @paused_times[key] = 0
40
+ end
41
+ yield
42
+ ensure
43
+ timing = @timings[key]
44
+ timing[:count] += 1
45
+ timing[:total_duration] += (Time.now - start) * 1000
46
+ if !nested
47
+ timing[:total_duration] -= @paused_times.delete(key)
48
+ end
49
+ end
50
+
51
+ # Pause execution timing while executing block
52
+ def pause(type, name)
53
+ key = {type: type, name: name}
54
+ start = Time.now
55
+ yield
56
+ ensure
57
+ @paused_times[key]+= (Time.now - start) * 1000
58
+ end
59
+
60
+ def timings
61
+ total_key = {type: "request", name: "total"}
62
+ timing = @timings[total_key]
63
+ timing[:count] = 1
64
+ timing[:total_duration] = (Time.now - @start_time) * 1000
65
+
66
+ split_timings = {}
67
+ @timings.each do |key, value|
68
+ # Round to microsecond precision
69
+ value[:total_duration] = value[:total_duration].round(3)
70
+
71
+ split_timings[key[:type]] ||= {}
72
+ split_timings[key[:type]][key[:name]] = value
73
+ end
74
+ split_timings
75
+ end
76
+
77
+ def should_report?
78
+ result = Immunio.run_hook "request", "should_report"
79
+ # If the hook doesn't exist, turn a nil report value into a false value
80
+ report = !!result["report"]
81
+ Immunio.logger.debug "Report request: #{report}"
82
+ report
83
+ end
84
+
85
+ # Encode the request data to send it to the channel.
86
+ def encode
87
+ if @data.is_a? Hash
88
+ @data.to_msgpack
89
+ else # Lua table
90
+ # Encode the data in Lua to avoid pulling a huge Lua table out of the VM, which is slow.
91
+ @vm.unsafe_call_by_name 'encode', @data
92
+ end
93
+ end
94
+
95
+ def self.current
96
+ Thread.current["immunio.request"]
97
+ end
98
+
99
+ def self.current=(request)
100
+ Thread.current["immunio.request"] = request
101
+ Immunio.logger.debug "Setting current request for thread #{Thread.current.object_id} to #{request && request.id || nil}"
102
+ end
103
+
104
+ # This shim exists to preserve the stack depth into hooks when self.time and self.pause are
105
+ # called. Otherwise the number of immunio frames will vary based on the state of the request object
106
+ def self.stack_shim()
107
+ yield
108
+ end
109
+
110
+ def self.time(type, name, &block)
111
+ if Request.current
112
+ Request.current.time(type, name, &block)
113
+ else
114
+ Immunio.logger.debug { "Starting request time for #{type} #{name}" }
115
+ self.stack_shim(&block)
116
+ end
117
+ end
118
+
119
+ def self.pause(type, name, &block)
120
+ if Request.current
121
+ Request.current.pause(type, name, &block)
122
+ else
123
+ self.stack_shim(&block)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def internal_id
130
+ return @internal_id if defined?(@internal_id)
131
+
132
+ if @env['HTTP_X_REQUEST_ID']
133
+ @internal_id = @env['HTTP_X_REQUEST_ID'].gsub(/[^\w\-]/, '').first(255)
134
+ else
135
+ @internal_id = SecureRandom.uuid
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,27 @@
1
+ module Rufus::Lua
2
+ # Auto-unref Lua ref when it goes out of scope in Ruby.
3
+ class Ref
4
+ def initialize_with_finalizer(*args)
5
+ initialize_without_finalizer(*args)
6
+
7
+ ObjectSpace.define_finalizer self, self.class.finalize(@pointer, @ref)
8
+ end
9
+ alias_method :initialize_without_finalizer, :initialize
10
+ alias_method :initialize, :initialize_with_finalizer
11
+
12
+ def self.finalize(pointer, ref)
13
+ # Hack to test if the Lua state was closed and de-refed.
14
+ original_pointer_address = pointer.read_pointer
15
+ proc do
16
+ Lib.luaL_unref(pointer, LUA_REGISTRYINDEX, ref) if original_pointer_address == pointer.read_pointer
17
+ end
18
+ end
19
+
20
+ undef :free
21
+ def free
22
+ # Noop. Taken care of by finalizer. Should not conflict.
23
+ end
24
+
25
+ public :load_onto_stack
26
+ end
27
+ end
@@ -0,0 +1,157 @@
1
+ module Rufus::Lua
2
+ class State
3
+ # Load the code into a function and return it.
4
+ # Safer than evaling some string.
5
+ def load(code, filename=nil)
6
+ err = Lib.luaL_loadbuffer(@pointer, code, code.size, filename)
7
+ fail_if_error('load:compile', err, nil, filename, nil)
8
+
9
+ function = stack_fetch
10
+ stack_pop
11
+
12
+ function
13
+ end
14
+ end
15
+
16
+ class HashWithNilKeyError < StandardError
17
+ end
18
+
19
+ module StateMixin
20
+ # Expose error_handler to set it manually on Functions since it is not inherited from State.
21
+ attr_accessor :error_handler
22
+
23
+ protected
24
+
25
+ # Fix stack_fetch so it doesn't pop a value off the top for tables,
26
+ # functions, and threads.
27
+ #
28
+ # FIXME: pos isn't honored for tables, functions, or threads.
29
+ undef :stack_fetch
30
+ def stack_fetch(pos=-1)
31
+ type, tname = stack_type_at(pos)
32
+
33
+ case type
34
+
35
+ when TNIL then nil
36
+
37
+ when TSTRING then
38
+ string = nil
39
+ FFI::MemoryPointer.new(:size_t) do |len|
40
+ ptr = Lib.lua_tolstring(@pointer, pos, len)
41
+ string = ptr.read_string(len.read_long)
42
+ end
43
+ string
44
+
45
+ when TBOOLEAN then (Lib.lua_toboolean(@pointer, pos) == 1)
46
+ when TNUMBER then Lib.lua_tonumber(@pointer, pos)
47
+
48
+ when TTABLE then
49
+ table = Table.new(@pointer)
50
+ table.load_onto_stack
51
+ table
52
+
53
+ when TFUNCTION then
54
+ func = Function.new(@pointer)
55
+ func.load_onto_stack
56
+ func
57
+
58
+ when TTHREAD then
59
+ coroutine = Coroutine.new(@pointer)
60
+ coroutine.load_onto_stack
61
+ coroutine
62
+
63
+ else tname
64
+ end
65
+ end
66
+
67
+ # Now that we've fixed popping in stack_fetch above, always remove the top
68
+ # item from the stack.
69
+ undef :stack_pop
70
+ def stack_pop
71
+ r = stack_fetch
72
+ stack_unstack
73
+ r
74
+ end
75
+
76
+ # This method is effectively a pop without retrieving the top value.
77
+ # Upstream papers over stack corruption by not letting the stack pointer
78
+ # go negative, but this makes our tests erroneously pass when they check
79
+ # for stack corruption. Instead, do the right thing, and catch problems in
80
+ # tests.
81
+ undef :stack_unstack
82
+ def stack_unstack
83
+ Lib.lua_settop(@pointer, -2)
84
+ end
85
+
86
+ # Patch `stack_push` to support passing Lua objects (functions, tables)
87
+ # back into the VM. Also, support conversion of Pathname objects.
88
+ def stack_push_with_ref(o)
89
+ case o
90
+ when Ref
91
+ o.load_onto_stack
92
+ when Pathname
93
+ stack_push_without_ref o.to_s
94
+ when File
95
+ stack_push_without_ref o.path
96
+ when ActiveSupport::Multibyte::Chars
97
+ stack_push_without_ref o.to_s
98
+ else
99
+ begin
100
+ stack_push_without_ref o
101
+ rescue ArgumentError
102
+ # Rufus-lua doesn't know how to handle it, so call inspect which
103
+ # should always return a string describing the object. Usually we
104
+ # just need a placeholder for such objects, so this will do.
105
+ stack_push_without_ref o.inspect
106
+ end
107
+ end
108
+ end
109
+ alias_method :stack_push_without_ref, :stack_push
110
+ alias_method :stack_push, :stack_push_with_ref
111
+
112
+ # Patch `stack_fetch` to set encoding to strings.
113
+ def stack_fetch_with_unicode(pos=-1)
114
+ type = stack_type_at(pos)
115
+
116
+ case type[0]
117
+ when TSTRING
118
+ string = nil
119
+ FFI::MemoryPointer.new(:size_t) do |len|
120
+ ptr = Lib.lua_tolstring(@pointer, pos, len)
121
+ string = ptr.read_string(len.read_long)
122
+ end
123
+ string.force_encoding(Encoding.default_internal)
124
+ else
125
+ stack_fetch_without_unicode pos
126
+ end
127
+ end
128
+ alias_method :stack_fetch_without_unicode, :stack_fetch
129
+ alias_method :stack_fetch, :stack_fetch_with_unicode
130
+
131
+ # Trying to push a hash with a nil key causes a Lua panic. Instead, raise
132
+ # an exception in Ruby which we can deal with.
133
+ def stack_push_hash_with_immunio(h)
134
+ if h.has_key? nil
135
+ raise HashWithNilKeyError.new "can't pass hash with nil key into Lua"
136
+ end
137
+ stack_push_hash_without_immunio h
138
+ end
139
+ alias_method :stack_push_hash_without_immunio, :stack_push_hash
140
+ alias_method :stack_push_hash, :stack_push_hash_with_immunio
141
+ end
142
+
143
+ class Function
144
+ # If an argument fails to be converted for Lua, we end up leaking everything
145
+ # that was already pushed onto the stack for the call. Make sure we reset
146
+ # our stack.
147
+ def call_with_stack_reset(*args)
148
+ begin
149
+ call_without_stack_reset(*args)
150
+ rescue
151
+ Lib.lua_settop(@pointer, 0)
152
+ raise
153
+ end
154
+ end
155
+ alias_method_chain :call, :stack_reset
156
+ end
157
+ end
@@ -0,0 +1,137 @@
1
+ module Rufus::Lua
2
+ class Table
3
+ # Method to behave like Hash#fetch.
4
+ def fetch(key, default=nil)
5
+ value = self[key]
6
+ if value.nil?
7
+ default
8
+ else
9
+ value
10
+ end
11
+ end
12
+
13
+ def has_key?(key)
14
+ self.fetch(key) != nil
15
+ end
16
+
17
+ # Fix memory leak
18
+ undef :"[]"
19
+ def [](k)
20
+ # Push table onto stack
21
+ load_onto_stack
22
+
23
+ # Push key onto stack
24
+ stack_push(k)
25
+
26
+ # Fetches the value from the table, replaces the key at the top of the
27
+ # stack with the value. At the end, the stack still has the table at -2.
28
+ Lib.lua_gettable(@pointer, -2)
29
+
30
+ # Pop value off stack
31
+ v = stack_pop
32
+
33
+ # Pop table off stack
34
+ stack_pop # <= Memory leak fixed here
35
+
36
+ v
37
+ end
38
+
39
+ # Fix memory leak
40
+ undef :"[]="
41
+ def []=(k, v)
42
+ # Push table onto stack
43
+ load_onto_stack
44
+
45
+ stack_push(k)
46
+ stack_push(v)
47
+
48
+ # For the VM heavy thread test, we need to cause out-of-order execution in
49
+ # methods modifying the Lua stack. Here we tell Ruby to swap out threads
50
+ # if we are in the thread test.
51
+ Thread.pass if $IMMUNIO_IN_THREAD_TEST
52
+
53
+ # Sets the key-value pair onto the table, then pops the key and value but
54
+ # leaves the table on the stack
55
+ Lib.lua_settable(@pointer, -3)
56
+
57
+ # Pop table off stack
58
+ stack_pop # <= Memory leak fixed here
59
+
60
+ v
61
+ end
62
+
63
+ # Fix memory leak
64
+ undef :objlen
65
+ def objlen
66
+ # Push table onto stack
67
+ load_onto_stack
68
+
69
+ # Gets the length of the table, but leaves the table on the stack
70
+ len = Lib.lua_objlen(@pointer, -1)
71
+
72
+ # Pop table off stack
73
+ stack_pop # <= Memory leak fixed here
74
+
75
+ len
76
+ end
77
+
78
+ # Override original to_h to fix bad implementation and convert nested
79
+ # collections.
80
+ # See http://www.lua.org/manual/5.1/manual.html#lua%5Fnext for details on
81
+ # this crazy complex iteration system in lua...
82
+ undef :to_h
83
+ def to_h
84
+ # Load table onto stack
85
+ load_onto_stack
86
+
87
+ # Get position of table on stack
88
+ table_pos = stack_top
89
+
90
+ # This is the starter for the lua_next iterator. Push a nil value as a
91
+ # sentinel onto the stack. This tells lua_next to fetch the first key-
92
+ # value pair from the table.
93
+ Lib.lua_pushnil(@pointer)
94
+
95
+ h = {}
96
+
97
+ # Fetch the next key-value pair. The last key must be on the top of the
98
+ # stack. The key on the stack will be overwritten with the next key
99
+ # fetched from the table. The value will be pushed onto the stack after
100
+ # the key.
101
+ #
102
+ # In order to iterate, the caller must pop the value off the stack.
103
+ # lua_next then uses the current key, which is once again at the top of
104
+ # the stack, to locate the next key.
105
+ #
106
+ # But! When there are no more key-value pairs, this call will remove the
107
+ # key from the stack, leaving the table at the top.
108
+ #
109
+ # WARNING: Lua keys can be anything (yes, that includes a table). We do
110
+ # not support tables as keys. Keys must be nice numbers or strings.
111
+ while Lib.lua_next(@pointer, table_pos) != 0 do
112
+ # Grab key and value from top of stack
113
+ value = stack_fetch(-1)
114
+ key = stack_fetch(-2)
115
+
116
+ # Pop the value off the stack leaving the key on top
117
+ stack_unstack
118
+
119
+ # Recurse into table if needed
120
+ if Table === value
121
+ if value[1]
122
+ h[key] = value.to_a
123
+ else
124
+ h[key] = value.to_h
125
+ end
126
+ else
127
+ h[key] = value
128
+ end
129
+ end
130
+
131
+ # Remove the table pushed onto the stack at the very beginning
132
+ Lib.lua_remove(@pointer, table_pos)
133
+
134
+ h
135
+ end
136
+ end
137
+ end