sqreen-alt 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +22 -0
  3. data/README.md +77 -0
  4. data/Rakefile +20 -0
  5. data/lib/sqreen-alt.rb +1 -0
  6. data/lib/sqreen.rb +68 -0
  7. data/lib/sqreen/attack_detected.html +2 -0
  8. data/lib/sqreen/binding_accessor.rb +288 -0
  9. data/lib/sqreen/ca.crt +72 -0
  10. data/lib/sqreen/call_countable.rb +67 -0
  11. data/lib/sqreen/callback_tree.rb +78 -0
  12. data/lib/sqreen/callbacks.rb +100 -0
  13. data/lib/sqreen/capped_queue.rb +23 -0
  14. data/lib/sqreen/condition_evaluator.rb +235 -0
  15. data/lib/sqreen/conditionable.rb +50 -0
  16. data/lib/sqreen/configuration.rb +168 -0
  17. data/lib/sqreen/context.rb +26 -0
  18. data/lib/sqreen/deliveries/batch.rb +84 -0
  19. data/lib/sqreen/deliveries/simple.rb +39 -0
  20. data/lib/sqreen/event.rb +16 -0
  21. data/lib/sqreen/events/attack.rb +61 -0
  22. data/lib/sqreen/events/remote_exception.rb +54 -0
  23. data/lib/sqreen/events/request_record.rb +62 -0
  24. data/lib/sqreen/exception.rb +34 -0
  25. data/lib/sqreen/frameworks.rb +40 -0
  26. data/lib/sqreen/frameworks/generic.rb +446 -0
  27. data/lib/sqreen/frameworks/rails.rb +148 -0
  28. data/lib/sqreen/frameworks/rails3.rb +36 -0
  29. data/lib/sqreen/frameworks/request_recorder.rb +69 -0
  30. data/lib/sqreen/frameworks/sinatra.rb +57 -0
  31. data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
  32. data/lib/sqreen/instrumentation.rb +542 -0
  33. data/lib/sqreen/log.rb +119 -0
  34. data/lib/sqreen/metrics.rb +6 -0
  35. data/lib/sqreen/metrics/average.rb +39 -0
  36. data/lib/sqreen/metrics/base.rb +45 -0
  37. data/lib/sqreen/metrics/collect.rb +22 -0
  38. data/lib/sqreen/metrics/sum.rb +20 -0
  39. data/lib/sqreen/metrics_store.rb +96 -0
  40. data/lib/sqreen/middleware.rb +34 -0
  41. data/lib/sqreen/payload_creator.rb +137 -0
  42. data/lib/sqreen/performance_notifications.rb +86 -0
  43. data/lib/sqreen/performance_notifications/log.rb +36 -0
  44. data/lib/sqreen/performance_notifications/metrics.rb +36 -0
  45. data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
  46. data/lib/sqreen/remote_command.rb +93 -0
  47. data/lib/sqreen/rule_attributes.rb +26 -0
  48. data/lib/sqreen/rule_callback.rb +108 -0
  49. data/lib/sqreen/rules.rb +126 -0
  50. data/lib/sqreen/rules_callbacks.rb +29 -0
  51. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
  52. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
  53. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
  54. data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
  55. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
  56. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
  57. data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
  58. data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
  59. data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
  60. data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
  61. data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
  62. data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
  63. data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
  64. data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
  65. data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
  66. data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
  67. data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
  68. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
  69. data/lib/sqreen/rules_signature.rb +151 -0
  70. data/lib/sqreen/runner.rb +365 -0
  71. data/lib/sqreen/runtime_infos.rb +138 -0
  72. data/lib/sqreen/safe_json.rb +60 -0
  73. data/lib/sqreen/sdk.rb +22 -0
  74. data/lib/sqreen/serializer.rb +46 -0
  75. data/lib/sqreen/session.rb +317 -0
  76. data/lib/sqreen/shared_storage.rb +31 -0
  77. data/lib/sqreen/stats.rb +18 -0
  78. data/lib/sqreen/version.rb +5 -0
  79. metadata +148 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8e911ef9a6f8fc1dff0ec458f44860f9c43acce72c704f48865e7170dbf5e99f
4
+ data.tar.gz: 6df56f16bfdddcdbf28b68b23c75d904a5a736d77ba9f4d4cef8b7ac7ef373e3
5
+ SHA512:
6
+ metadata.gz: 7985d8110bf3fb387fd14d17938e46439a33e503bb704dd801156d16444944e6a2e5df4a1fca0e736cc8e624bea365440573857af17985d51fd2acf6ac82fa8a
7
+ data.tar.gz: 80a78aa06bbfedbcd1a27c5b64877e4022669aac3ac6a4c5fdcfa458508c43936076ea1111804f9e57cfe1f8d446e3f4083709613d8a432e7582b2c57b9e5fef
@@ -0,0 +1,22 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
6
+
7
+ Examples of unacceptable behavior by participants include:
8
+
9
+ * The use of sexualized language or imagery
10
+ * Personal attacks
11
+ * Trolling or insulting/derogatory comments
12
+ * Public or private harassment
13
+ * Publishing other's private information, such as physical or electronic addresses, without explicit permission
14
+ * Other unethical or unprofessional conduct.
15
+
16
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
17
+
18
+ This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
19
+
20
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
21
+
22
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
@@ -0,0 +1,77 @@
1
+ # Sqreen
2
+
3
+ Auto protection for you application.
4
+
5
+ Copyright (c) 2015 Sqreen. All Rights Reserved.
6
+ Please refer to our terms for more information: https://www.sqreen.io/terms.html
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'sqreen'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install sqreen
23
+
24
+ ## Configuration
25
+
26
+ The only required parameter is your application's `token`.
27
+
28
+ ### By file
29
+ - for Rails:
30
+ ```shell
31
+ $ echo token: your_token > /path/to/RailsApp/config/sqreen.yml
32
+ ```
33
+ - for anything else:
34
+ ```shell
35
+ $ echo token: your_token > ~/sqreen.yml
36
+ ```
37
+
38
+ ### By environment:
39
+ ```shell
40
+ $ export SQREEN_TOKEN=your_token
41
+ ```
42
+
43
+ The following can be set:
44
+
45
+ *file* | *environment*
46
+ ------------|-------------
47
+ token | SQREEN_TOKEN
48
+ url | SQREEN_URL
49
+ verbosity | SQREEN_VERBOSITY
50
+ local_rules | SQREEN_RULES
51
+
52
+ SQREEN_RULES allows the agent to use rules that do not come from the server, but
53
+ from a local file.
54
+
55
+ ## Usage
56
+
57
+ TODO: Write usage instructions here
58
+
59
+ ## Development
60
+
61
+ ```shell
62
+ $ gem install bundler
63
+ $ bundle
64
+ ```
65
+
66
+ Check that everything is all right:
67
+ ```shell
68
+ $ bundle exec rake test
69
+ ```
70
+
71
+ Use `bin/console` for an interactive prompt that will allow you to experiment.
72
+
73
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
74
+
75
+ ## Contributing
76
+
77
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sqreen/RubyAgent. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'bundler/gem_tasks'
5
+ require 'rake/testtask'
6
+
7
+ if RUBY_VERSION >= '1.9.3'
8
+ require 'ci/reporter/rake/minitest'
9
+ task :testunit => 'ci:setup:minitest'
10
+ else
11
+ task :testunit => :test
12
+ end
13
+
14
+ Rake::TestTask.new do |t|
15
+ t.pattern = 'test/**/*.rb'
16
+ t.libs << 'test'
17
+ end
18
+
19
+ desc 'Run tests'
20
+ task :default => :test
@@ -0,0 +1 @@
1
+ require "sqreen"
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/instrumentation'
5
+ require 'sqreen/session'
6
+ require 'sqreen/runner'
7
+ require 'sqreen/callbacks'
8
+ require 'sqreen/version'
9
+ require 'sqreen/log'
10
+ require 'sqreen/stats'
11
+ require 'sqreen/exception'
12
+ require 'sqreen/configuration'
13
+ require 'sqreen/events/attack'
14
+ require 'sqreen/sdk'
15
+
16
+ require 'thread'
17
+
18
+ # Auto start the instrumentation.
19
+
20
+ Sqreen.framework.on_start do |framework|
21
+ Thread.new do
22
+ begin
23
+ runner = nil
24
+ configuration = Sqreen.config_init(framework)
25
+ Sqreen.log.debug("Starting Sqreen #{Sqreen::VERSION}")
26
+ framework.sqreen_configuration = configuration
27
+ prevent_startup = Sqreen.framework.prevent_startup
28
+ if !prevent_startup
29
+ runner = Sqreen::Runner.new(configuration, framework)
30
+ runner.run_watcher
31
+ else
32
+ Sqreen.log.debug("#{prevent_startup} prevented Sqreen startup")
33
+ end
34
+ rescue Sqreen::TokenNotFoundException
35
+ Sqreen.log.error "Sorry but we couldn't find your Sqreen token.\nYour application is NOT currently protected by Sqreen.\n\nHave you filled your config/sqreen.yml?\n\n"
36
+ rescue Sqreen::TokenInvalidException
37
+ Sqreen.log.error "Sorry but your Sqreen token appears to be invalid.\nYour application is NOT currently protected by Sqreen.\n\nHave you correctly filled your config/sqreen.yml?\n\n"
38
+ rescue Exception => e
39
+ Sqreen.log.error e.inspect
40
+ Sqreen.log.debug e.backtrace.join("\n")
41
+ if runner
42
+ # immediately post exception
43
+ runner.session.post_sqreen_exception(Sqreen::RemoteException.new(e))
44
+ Sqreen.log.debug("runner = #{runner.inspect}")
45
+ begin
46
+ runner.remove_instrumentation
47
+ rescue => remove_exception
48
+ Sqreen.log.debug(remove_exception.inspect)
49
+ # We did not manage to remove instrumentation, state is unclear:
50
+ # terminate thread
51
+ return nil
52
+ end
53
+ begin
54
+ runner.logout(false)
55
+ rescue => logout_exception
56
+ Sqreen.log.debug(logout_exception.inspect)
57
+ nil
58
+ end
59
+ end
60
+ # Wait a few seconds before retrying
61
+ delay = rand(120)
62
+ Sqreen.log.debug("Sleeping #{delay} seconds before retry")
63
+ sleep(delay)
64
+ retry
65
+ end
66
+ Sqreen.log.debug("shutting down Sqreen #{Sqreen::VERSION}")
67
+ end
68
+ end unless Sqreen::to_bool(ENV['SQREEN_DISABLE'])
@@ -0,0 +1,2 @@
1
+ <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sqreen has detected an attack.</title> <style>html, body, div, span, h1, a{margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline}body{background: -webkit-radial-gradient(26% 19%, circle, #fff, #f4f7f9); background: radial-gradient(circle at 26% 19%, #fff, #f4f7f9); display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -ms-flex-line-pack: center; align-content: center; width: 100%; min-height: 100vh; line-height: 1}svg, h1, p{display: block}svg{margin: 0 auto 4vh}h1{font-family: sans-serif; font-weight: 300; font-size: 34px; color: #384886; line-height: normal}p{font-size: 18px; line-height: normal; color: #b8bccc; font-family: sans-serif; font-weight: 300}a{color: #b8bccc}.flex{text-align: center}</style></head><body> <div class="flex"> <svg xmlns="http://www.w3.org/2000/svg" width="230" height="250" viewBox="0 0 230 250" enable-background="new 0 0 230 250"> <style>.st0{opacity: 0.4; filter: url(#a);}.st1{fill: #FFFFFF;}.st2{fill: #B0ACFF;}.st3{fill: #4842B7;}.st4{fill: #1E0936;}</style> <filter id="a" width="151.7%" height="146%" x="-25.8%" y="-16%" filterUnits="objectBoundingBox"> <feOffset dy="14" in="SourceAlpha" result="shadowOffsetOuter1"/> <feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="13"/> <feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/> </filter> <g class="st0"> <path id="b_2_" d="M202.6 34.9c-.2-1.2-.8-2.1-1.9-2.8-3.8-2-37.9-20.1-85.7-20.1-48.8 0-84.2 19.3-85.7 20.1-1 .6-1.6 1.6-1.8 2.7-14.8 123.2 84.7 176.3 85.7 176.8.6.3 1.2.4 1.8.4.6 0 1.2-.1 1.7-.4 1-.5 100.4-55 85.9-176.7z"/> </g> <path id="b_1_" d="M202.6 34.9c-.2-1.2-.8-2.1-1.9-2.8-3.8-2-37.9-20.1-85.7-20.1-48.8 0-84.2 19.3-85.7 20.1-1 .6-1.6 1.6-1.8 2.7-14.8 123.2 84.7 176.3 85.7 176.8.6.3 1.2.4 1.8.4.6 0 1.2-.1 1.7-.4 1-.5 100.4-55 85.9-176.7z" class="st1"/> <g id="nest-cmyk-indigo"> <ellipse id="sqreen" cx="115.5" cy="69.9" class="st2" rx="12.7" ry="12.7"/> <path id="app" d="M113.6 91.9V71.5L95.5 61.1v18l6.4-3.7c.5 1.1 1 2.2 1.7 3.2L97 82.3l16.6 9.6zm3.7 0l16.6-9.6-6.7-3.9c.7-1 1.3-2 1.7-3.2l6.4 3.7v-18l-18.1 10.5v20.5zM96.9 57.6l18.6 10.7L134 57.6 117.3 48v7.6c-.6-.1-1.2-.1-1.8-.1-.6 0-1.2 0-1.8.1V48l-16.8 9.6zm20.2-13.9l20.3 11.7c1 .6 1.6 1.7 1.6 2.8v23.5c0 1.2-.6 2.2-1.6 2.8l-20.3 11.7c-1 .6-2.3.6-3.3 0L93.5 84.5c-1-.6-1.6-1.7-1.6-2.8V58.2c0-1.2.6-2.2 1.6-2.8l20.3-11.7c1-.6 2.3-.6 3.3 0z" class="st3"/> </g> <path id="s" d="M74.6 113c-1.8-1-3.5-1.5-5.2-1.5-1.4 0-2.3.6-2.3 1.5 0 2.7 10.1.4 10.1 7.7 0 3.3-2.9 6-7.6 6-2.1 0-4.7-.5-6.4-1.4l-.1-.1c-.3-.2-.3-.5-.2-.8l1.2-2.7c.1-.3.5-.5.9-.3.1 0 .1.1.2.1 1.5.6 3.1 1 4.6 1 2.2 0 2.9-.6 2.9-1.7 0-3-10.1-.8-10.1-7.7 0-3.1 2.7-5.8 7-5.8 2.1 0 5 .7 6.9 1.8.1 0 .1.1.2.1.3.2.4.5.3.8l-1.2 2.7c-.1.3-.5.5-.9.3h-.3z" class="st4"/> <path id="q" d="M93.6 107.8h3.2c.4 0 .7.3.7.7v25.9c0 .4-.3.7-.7.7h-3.2c-.4 0-.7-.3-.7-.7v-9.1c-1.2.8-2.9 1.4-4.7 1.4-5.4 0-9.6-4.3-9.6-9.7 0-5.4 4.1-9.7 9.6-9.7 1.8 0 3.5.6 4.7 1.4v-.1c0-.5.3-.8.7-.8zm-.7 12.4v-6.5c-1.3-1.3-2.8-2.1-4.5-2.1-2.9 0-5.1 2.3-5.1 5.4s2.2 5.4 5.1 5.4c1.7-.1 3.2-.7 4.5-2.2z" class="st4"/> <path id="r" d="M112.5 107.8c-1-.4-2-.6-3-.6-1.8 0-3.5.6-4.9 1.4v-.2c0-.3-.2-.5-.5-.5h-3.4c-.3 0-.5.2-.5.5v17.8c0 .3.2.5.5.5h3.4c.3 0 .5-.2.5-.5v-12.6c1.1-1.2 2.8-1.9 4.6-1.9.4 0 .9 0 1.5.2.3.1.6-.1.7-.4l1.3-2.9c.1-.4 0-.7-.2-.8z" class="st4"/> <path id="e" d="M129 124.7c-1.7 1-4.2 2-6.7 2-6 0-10.3-4.4-10.3-9.9 0-5.3 3.7-9.6 9.4-9.6 5.2 0 8.4 4.4 8.4 9 0 .4 0 .9-.1 1.2 0 .3-.3.6-.7.6h-12.5c.5 2.8 2.8 4.5 5.8 4.5 1.7 0 3.4-.5 5.1-1.4.3-.2.6-.1.8.2l1.2 2.6c.1.2 0 .4-.2.6-.2.1-.2.2-.2.2zm-12.4-10h8.5c-.2-1.8-1.9-3.3-3.9-3.3-2.5-.1-4 1.4-4.6 3.3z" class="st4"/> <path id="e_1_" d="M148.7 124.7c-1.7 1-4.2 2-6.7 2-6 0-10.3-4.4-10.3-9.9 0-5.3 3.7-9.6 9.4-9.6 5.2 0 8.4 4.4 8.4 9 0 .4 0 .9-.1 1.2 0 .3-.3.6-.7.6h-12.5c.5 2.8 2.8 4.5 5.8 4.5 1.7 0 3.4-.5 5.1-1.4.3-.2.6-.1.8.2l1.2 2.6c.1.2 0 .4-.2.6-.2.1-.2.2-.2.2zm-12.4-10h8.5c-.2-1.8-1.9-3.3-3.9-3.3-2.5-.1-4 1.4-4.6 3.3z" class="st4"/> <path id="n" d="M151.5 108.5V126c0 .4.3.7.7.7h3.2c.4 0 .7-.3.7-.7v-12.5c1.1-1.2 2.8-1.9 4.6-1.9 2.9 0 4.5 1.6 4.5 4.7v9.7c0 .4.3.7.7.7h3.2c.4 0 .7-.3.7-.7v-10.2c0-5.2-2.9-8.5-8.8-8.5-1.8 0-3.5.6-4.9 1.4v-.1c0-.4-.3-.7-.7-.7h-3.2c-.4-.1-.7.2-.7.6z" class="st4"/> </svg> <h1>Uh Oh! Sqreen has detected an attack.</h1> <p>If you are the application owner, check the Sqreen <a href="https://my.sqreen.io/">dashboard</a> for more information.</p></div></body></html>
2
+
@@ -0,0 +1,288 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'strscan'
5
+ require 'sqreen/exception'
6
+ require 'set'
7
+
8
+ module Sqreen
9
+ # the value located at the given binding
10
+ class BindingAccessor
11
+ PathElem = Struct.new(:kind, :value)
12
+ attr_reader :path, :expression, :final_transform
13
+
14
+ # Expression to be accessed
15
+ # @param expression [String] expression to read
16
+ # @param convert [Boolean] wheter to convert objects to
17
+ # simpler types (Array, Hash, String...)
18
+ def initialize(expression, convert = false)
19
+ @final_transform = nil
20
+ @expression = expression
21
+ @path = []
22
+ @convert = convert
23
+ parse(expression)
24
+ end
25
+
26
+ # Access data from the expression
27
+ def access(binding, framework = nil, instance = nil, arguments = nil, cbdata = nil, last_return = nil)
28
+ env = [framework, instance, arguments, cbdata, last_return]
29
+ value = nil
30
+ @path.each do |component|
31
+ value = resolve_component(value, component, binding, env)
32
+ end
33
+ value
34
+ end
35
+
36
+ # access and transform expression for the given binding
37
+ def resolve(*args)
38
+ value = access(*args)
39
+ value = transform(value) if @final_transform
40
+ return convert(value) if @convert
41
+ value
42
+ end
43
+
44
+ protected
45
+
46
+ STRING_KIND = 'string'.freeze
47
+ SYMBOL_KIND = 'symbol'.freeze
48
+ INTEGER_KIND = 'integer'.freeze
49
+ LOCAL_VAR_KIND = 'local-variable'.freeze
50
+ INSTANCE_VAR_KIND = 'instance-variable'.freeze
51
+ CLASS_VAR_KIND = 'class-variable'.freeze
52
+ GLOBAL_VAR_KIND = 'global-variable'.freeze
53
+ CONSTANT_KIND = 'constant'.freeze
54
+ METHOD_KIND = 'method'.freeze
55
+ INDEX_KIND = 'index'.freeze
56
+ SQREEN_VAR_KIND = 'sqreen-variable'.freeze
57
+
58
+ if binding.respond_to?(:local_variable_get)
59
+ def get_local(name, bind)
60
+ bind.local_variable_get(name)
61
+ end
62
+ else
63
+ def get_local(name, bind)
64
+ eval(name, bind)
65
+ end
66
+ end
67
+
68
+ def resolve_component(current_value, component, binding, env)
69
+ case component[:kind]
70
+ when STRING_KIND, SYMBOL_KIND, INTEGER_KIND
71
+ component[:value]
72
+ when LOCAL_VAR_KIND
73
+ get_local(component[:value], binding)
74
+ when INSTANCE_VAR_KIND
75
+ current_value.instance_variable_get("@#{component[:value]}")
76
+ when CLASS_VAR_KIND
77
+ current_value.class.class_variable_get("@@#{component[:value]}")
78
+ when GLOBAL_VAR_KIND
79
+ instance_eval("$#{component[:value]}")
80
+ when CONSTANT_KIND
81
+ if current_value
82
+ current_value.const_get(component[:value].to_s)
83
+ else
84
+ Object.const_get(component[:value].to_s)
85
+ end
86
+ when METHOD_KIND
87
+ current_value.send(component[:value])
88
+ when INDEX_KIND
89
+ current_value[component[:value]]
90
+ when SQREEN_VAR_KIND
91
+ resolve_sqreen_variable(component[:value], *env)
92
+ else
93
+ raise "Do not know how to handle this component #{component.inspect}"
94
+ end
95
+ end
96
+
97
+ def resolve_sqreen_variable(what, framework, instance, args, cbdata, rv)
98
+ case what
99
+ when 'data'
100
+ cbdata
101
+ when 'rv'
102
+ rv
103
+ when 'args'
104
+ args
105
+ when 'inst'
106
+ instance
107
+ else
108
+ framework.send(what)
109
+ end
110
+ end
111
+
112
+ def parse(expression)
113
+ expression = extract_transform(expression)
114
+ @scan = StringScanner.new(expression)
115
+ until @scan.eos?
116
+ pos = @scan.pos
117
+ scalar = scan_scalar
118
+ if scalar
119
+ @path.push scalar
120
+ return
121
+ end
122
+ if @path.empty?
123
+ scan_push_variable
124
+ else
125
+ scan_push_method
126
+ end
127
+ scan_push_indexes
128
+ scan_push_more_constant if @scan.scan(/\./).nil?
129
+ raise Sqreen::Exception, error_state('Scan stuck') if @scan.pos == pos
130
+ end
131
+ ensure
132
+ @scan = nil
133
+ end
134
+
135
+ def extract_transform(expression)
136
+ parts = expression.split('|')
137
+ self.final_transform = parts.pop if parts.size > 1
138
+ parts.join('|').rstrip
139
+ end
140
+
141
+ def final_transform=(transform)
142
+ transform.strip!
143
+ unless KNOWN_TRANSFORMS.include?(transform)
144
+ raise Sqreen::Exception, "Invalid transform #{transform}"
145
+ end
146
+ @final_transform = transform
147
+ end
148
+
149
+ def scan_scalar
150
+ if @scan.scan(/\d+/)
151
+ PathElem.new(INTEGER_KIND, @scan[0].to_i)
152
+ elsif @scan.scan(/:(\w+)/)
153
+ PathElem.new(SYMBOL_KIND, @scan[1].to_sym)
154
+ elsif @scan.scan(/'((?:\\.|[^\\'])*)'/)
155
+ PathElem.new(STRING_KIND, @scan[1])
156
+ end
157
+ end
158
+
159
+ RUBY_IDENTIFIER_CHAR = if ''.respond_to? :encoding
160
+ '[\w\u0080-\u{10ffff}]'
161
+ else
162
+ '[\w\x80-\xFF]'
163
+ end
164
+
165
+ def scan_push_constant
166
+ return unless @scan.scan(/([A-Z]#{RUBY_IDENTIFIER_CHAR}+)/)
167
+ @path << PathElem.new(CONSTANT_KIND, @scan[1])
168
+ end
169
+
170
+ def scan_push_more_constant
171
+ while @scan.scan(/::/)
172
+ unless scan_push_constant
173
+ raise Sqreen::Exception, error_state('No more constant')
174
+ end
175
+ end
176
+ end
177
+
178
+ def scan_push_variable
179
+ if @scan.scan(/\$(#{RUBY_IDENTIFIER_CHAR}+)/)
180
+ @path << PathElem.new(GLOBAL_VAR_KIND, @scan[1])
181
+ elsif @scan.scan(/@@(#{RUBY_IDENTIFIER_CHAR}+)/)
182
+ @path << PathElem.new(CLASS_VAR_KIND, @scan[1])
183
+ elsif @scan.scan(/@(#{RUBY_IDENTIFIER_CHAR}+)/)
184
+ @path << PathElem.new(INSTANCE_VAR_KIND, @scan[1])
185
+ elsif @scan.scan(/#\.(\w+)/)
186
+ @path << PathElem.new(SQREEN_VAR_KIND, @scan[1])
187
+ elsif scan_push_constant
188
+ nil
189
+ elsif @scan.scan(/(#{RUBY_IDENTIFIER_CHAR}+)/u)
190
+ @path << PathElem.new(LOCAL_VAR_KIND, @scan[1])
191
+ end
192
+ end
193
+
194
+ def scan_push_method
195
+ if @scan.scan(/@@(#{RUBY_IDENTIFIER_CHAR}+)/)
196
+ @path << PathElem.new(CLASS_VAR_KIND, @scan[1])
197
+ elsif @scan.scan(/@(#{RUBY_IDENTIFIER_CHAR}+)/)
198
+ @path << PathElem.new(INSTANCE_VAR_KIND, @scan[1])
199
+ elsif @scan.scan(/(#{RUBY_IDENTIFIER_CHAR}+)/)
200
+ @path << PathElem.new(METHOD_KIND, @scan[1])
201
+ end
202
+ end
203
+
204
+ def scan_push_indexes
205
+ while @scan.scan(/\[/)
206
+ scalar = scan_scalar
207
+ raise Sqreen::Exception, error_state('Invalid index') unless scalar
208
+ unless @scan.scan(/\]/)
209
+ raise Sqreen::Exception, error_state('Unfinished index')
210
+ end
211
+ @path << PathElem.new(INDEX_KIND, scalar[:value])
212
+ end
213
+ end
214
+
215
+ def error_state(msg)
216
+ "#{msg} at #{@scan.pos} after #{@scan.string[0...@scan.pos]} (#{@scan.string})"
217
+ end
218
+
219
+ def convert(value)
220
+ case value
221
+ when ::Exception
222
+ { 'message' => value.message, 'backtrace' => value.backtrace }
223
+ else
224
+ value
225
+ end
226
+ end
227
+
228
+ # Available final transformations
229
+ module Transforms
230
+ def flat_keys(value, max_iter = 1000)
231
+ return nil if value.nil?
232
+ seen = Set.new
233
+ look_into = [value]
234
+ keys = []
235
+ idx = 0
236
+ until look_into.empty? || max_iter <= idx
237
+ idx += 1
238
+ val = look_into.pop
239
+ next unless seen.add?(val.object_id)
240
+ case val
241
+ when Hash
242
+ keys.concat(val.keys)
243
+ look_into.concat(val.values)
244
+ when Array
245
+ look_into.concat(val)
246
+ else
247
+ next if val.respond_to?(:seek)
248
+ val.each { |v| look_into << v } if val.respond_to?(:each)
249
+ end
250
+ end
251
+ keys
252
+ end
253
+
254
+ def flat_values(value, max_iter = 1000)
255
+ return nil if value.nil?
256
+ seen = Set.new
257
+ look_into = [value]
258
+ values = []
259
+ idx = 0
260
+ until look_into.empty? || max_iter <= idx
261
+ idx += 1
262
+ val = look_into.shift
263
+ next unless seen.add?(val.object_id)
264
+ case val
265
+ when Hash
266
+ look_into.concat(val.values)
267
+ when Array
268
+ look_into.concat(val)
269
+ else
270
+ next if val.respond_to?(:seek)
271
+ if val.respond_to?(:each)
272
+ val.each { |v| look_into << v }
273
+ else
274
+ values << val
275
+ end
276
+ end
277
+ end
278
+ values
279
+ end
280
+ end
281
+ include Transforms
282
+ KNOWN_TRANSFORMS = Transforms.public_instance_methods.map(&:to_s)
283
+
284
+ def transform(value)
285
+ send(@final_transform, value) if @final_transform
286
+ end
287
+ end
288
+ end