sqreen-alt 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +20 -0
- data/lib/sqreen-alt.rb +1 -0
- data/lib/sqreen.rb +68 -0
- data/lib/sqreen/attack_detected.html +2 -0
- data/lib/sqreen/binding_accessor.rb +288 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/call_countable.rb +67 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +100 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +235 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +168 -0
- data/lib/sqreen/context.rb +26 -0
- data/lib/sqreen/deliveries/batch.rb +84 -0
- data/lib/sqreen/deliveries/simple.rb +39 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +61 -0
- data/lib/sqreen/events/remote_exception.rb +54 -0
- data/lib/sqreen/events/request_record.rb +62 -0
- data/lib/sqreen/exception.rb +34 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +446 -0
- data/lib/sqreen/frameworks/rails.rb +148 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/request_recorder.rb +69 -0
- data/lib/sqreen/frameworks/sinatra.rb +57 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +542 -0
- data/lib/sqreen/log.rb +119 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +45 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +96 -0
- data/lib/sqreen/middleware.rb +34 -0
- data/lib/sqreen/payload_creator.rb +137 -0
- data/lib/sqreen/performance_notifications.rb +86 -0
- data/lib/sqreen/performance_notifications/log.rb +36 -0
- data/lib/sqreen/performance_notifications/metrics.rb +36 -0
- data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
- data/lib/sqreen/remote_command.rb +93 -0
- data/lib/sqreen/rule_attributes.rb +26 -0
- data/lib/sqreen/rule_callback.rb +108 -0
- data/lib/sqreen/rules.rb +126 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +77 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +44 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +40 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +24 -0
- data/lib/sqreen/rules_callbacks/custom_error.rb +64 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +241 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +22 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +25 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +138 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +39 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +254 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
- data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
- data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
- data/lib/sqreen/rules_signature.rb +151 -0
- data/lib/sqreen/runner.rb +365 -0
- data/lib/sqreen/runtime_infos.rb +138 -0
- data/lib/sqreen/safe_json.rb +60 -0
- data/lib/sqreen/sdk.rb +22 -0
- data/lib/sqreen/serializer.rb +46 -0
- data/lib/sqreen/session.rb +317 -0
- data/lib/sqreen/shared_storage.rb +31 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +5 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -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
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -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/)
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/sqreen-alt.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "sqreen"
|
data/lib/sqreen.rb
ADDED
@@ -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
|