hyper-pure-sys 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bullet-8.1.3/CHANGELOG.md +374 -0
- data/bullet-8.1.3/MIT-LICENSE +20 -0
- data/bullet-8.1.3/README.md +525 -0
- data/bullet-8.1.3/lib/bullet/active_job.rb +13 -0
- data/bullet-8.1.3/lib/bullet/active_record4.rb +197 -0
- data/bullet-8.1.3/lib/bullet/active_record41.rb +191 -0
- data/bullet-8.1.3/lib/bullet/active_record42.rb +262 -0
- data/bullet-8.1.3/lib/bullet/active_record5.rb +294 -0
- data/bullet-8.1.3/lib/bullet/active_record52.rb +278 -0
- data/bullet-8.1.3/lib/bullet/active_record60.rb +310 -0
- data/bullet-8.1.3/lib/bullet/active_record61.rb +310 -0
- data/bullet-8.1.3/lib/bullet/active_record70.rb +319 -0
- data/bullet-8.1.3/lib/bullet/active_record71.rb +319 -0
- data/bullet-8.1.3/lib/bullet/active_record72.rb +319 -0
- data/bullet-8.1.3/lib/bullet/active_record80.rb +319 -0
- data/bullet-8.1.3/lib/bullet/active_record81.rb +321 -0
- data/bullet-8.1.3/lib/bullet/bullet_xhr.js +64 -0
- data/bullet-8.1.3/lib/bullet/dependency.rb +165 -0
- data/bullet-8.1.3/lib/bullet/detector/association.rb +92 -0
- data/bullet-8.1.3/lib/bullet/detector/base.rb +8 -0
- data/bullet-8.1.3/lib/bullet/detector/counter_cache.rb +69 -0
- data/bullet-8.1.3/lib/bullet/detector/n_plus_one_query.rb +148 -0
- data/bullet-8.1.3/lib/bullet/detector/unused_eager_loading.rb +100 -0
- data/bullet-8.1.3/lib/bullet/detector.rb +11 -0
- data/bullet-8.1.3/lib/bullet/ext/object.rb +37 -0
- data/bullet-8.1.3/lib/bullet/ext/string.rb +14 -0
- data/bullet-8.1.3/lib/bullet/mongoid4x.rb +59 -0
- data/bullet-8.1.3/lib/bullet/mongoid5x.rb +59 -0
- data/bullet-8.1.3/lib/bullet/mongoid6x.rb +59 -0
- data/bullet-8.1.3/lib/bullet/mongoid7x.rb +74 -0
- data/bullet-8.1.3/lib/bullet/mongoid8x.rb +61 -0
- data/bullet-8.1.3/lib/bullet/mongoid9x.rb +74 -0
- data/bullet-8.1.3/lib/bullet/notification/base.rb +92 -0
- data/bullet-8.1.3/lib/bullet/notification/counter_cache.rb +15 -0
- data/bullet-8.1.3/lib/bullet/notification/n_plus_one_query.rb +31 -0
- data/bullet-8.1.3/lib/bullet/notification/unused_eager_loading.rb +31 -0
- data/bullet-8.1.3/lib/bullet/notification.rb +13 -0
- data/bullet-8.1.3/lib/bullet/notification_collector.rb +25 -0
- data/bullet-8.1.3/lib/bullet/rack.rb +186 -0
- data/bullet-8.1.3/lib/bullet/registry/association.rb +16 -0
- data/bullet-8.1.3/lib/bullet/registry/base.rb +46 -0
- data/bullet-8.1.3/lib/bullet/registry/call_stack.rb +17 -0
- data/bullet-8.1.3/lib/bullet/registry/object.rb +18 -0
- data/bullet-8.1.3/lib/bullet/registry.rb +10 -0
- data/bullet-8.1.3/lib/bullet/stack_trace_filter.rb +67 -0
- data/bullet-8.1.3/lib/bullet/version.rb +5 -0
- data/bullet-8.1.3/lib/bullet.rb +289 -0
- data/bullet-8.1.3/lib/generators/bullet/install_generator.rb +47 -0
- data/bullet-8.1.3/tasks/bullet_tasks.rake +11 -0
- data/hyper-pure-sys.gemspec +12 -0
- metadata +91 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Mongoid
|
|
3
|
+
def self.enable
|
|
4
|
+
require 'mongoid'
|
|
5
|
+
::Mongoid::Contextual::Mongo.class_eval do
|
|
6
|
+
alias_method :origin_first, :first
|
|
7
|
+
alias_method :origin_last, :last
|
|
8
|
+
alias_method :origin_each, :each
|
|
9
|
+
alias_method :origin_eager_load, :eager_load
|
|
10
|
+
|
|
11
|
+
def first(limit = nil)
|
|
12
|
+
result = origin_first(limit)
|
|
13
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def last(limit = nil)
|
|
18
|
+
result = origin_last(limit)
|
|
19
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each(&block)
|
|
24
|
+
return to_enum unless block_given?
|
|
25
|
+
|
|
26
|
+
records = []
|
|
27
|
+
origin_each { |record| records << record }
|
|
28
|
+
if records.length > 1
|
|
29
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
30
|
+
elsif records.size == 1
|
|
31
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
32
|
+
end
|
|
33
|
+
records.each(&block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def eager_load(docs)
|
|
37
|
+
associations = criteria.inclusions.map(&:name)
|
|
38
|
+
docs.each do |doc|
|
|
39
|
+
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
|
40
|
+
end
|
|
41
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
42
|
+
origin_eager_load(docs)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
::Mongoid::Association::Accessors.class_eval do
|
|
47
|
+
alias_method :origin_get_relation, :get_relation
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def get_relation(name, association, object, reload = false)
|
|
52
|
+
result = origin_get_relation(name, association, object, reload)
|
|
53
|
+
unless association.embedded?
|
|
54
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
|
55
|
+
end
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Mongoid
|
|
5
|
+
def self.enable
|
|
6
|
+
require 'mongoid'
|
|
7
|
+
require 'rubygems'
|
|
8
|
+
::Mongoid::Contextual::Mongo.class_eval do
|
|
9
|
+
alias_method :origin_first, :first
|
|
10
|
+
alias_method :origin_last, :last
|
|
11
|
+
alias_method :origin_each, :each
|
|
12
|
+
alias_method :origin_eager_load, :eager_load
|
|
13
|
+
|
|
14
|
+
%i[first last].each do |context|
|
|
15
|
+
default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}
|
|
16
|
+
define_method(context) do |opts = default|
|
|
17
|
+
result = send(:"origin_#{context}", opts)
|
|
18
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each(&_block)
|
|
24
|
+
return to_enum unless block_given?
|
|
25
|
+
|
|
26
|
+
first_document = nil
|
|
27
|
+
document_count = 0
|
|
28
|
+
|
|
29
|
+
origin_each do |document|
|
|
30
|
+
document_count += 1
|
|
31
|
+
|
|
32
|
+
if document_count == 1
|
|
33
|
+
first_document = document
|
|
34
|
+
elsif document_count == 2
|
|
35
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
|
|
36
|
+
yield(first_document)
|
|
37
|
+
first_document = nil
|
|
38
|
+
yield(document)
|
|
39
|
+
else
|
|
40
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
|
|
41
|
+
yield(document)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if document_count == 1
|
|
46
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
|
|
47
|
+
yield(first_document)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def eager_load(docs)
|
|
54
|
+
associations = criteria.inclusions.map(&:name)
|
|
55
|
+
docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
|
|
56
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
57
|
+
origin_eager_load(docs)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
::Mongoid::Association::Accessors.class_eval do
|
|
62
|
+
alias_method :origin_get_relation, :get_relation
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def get_relation(name, association, object, reload = false)
|
|
67
|
+
result = origin_get_relation(name, association, object, reload)
|
|
68
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
|
|
69
|
+
result
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Notification
|
|
5
|
+
class Base
|
|
6
|
+
attr_accessor :notifier, :url
|
|
7
|
+
attr_reader :base_class, :associations, :path
|
|
8
|
+
|
|
9
|
+
def initialize(base_class, association_or_associations, path = nil)
|
|
10
|
+
@base_class = base_class
|
|
11
|
+
@associations =
|
|
12
|
+
association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
|
|
13
|
+
@path = path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def title
|
|
17
|
+
raise NoMethodError, 'no method title defined'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def body
|
|
21
|
+
raise NoMethodError, 'no method body defined'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call_stack_messages
|
|
25
|
+
''
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def whoami
|
|
29
|
+
@user ||=
|
|
30
|
+
ENV['USER'].presence ||
|
|
31
|
+
(
|
|
32
|
+
begin
|
|
33
|
+
`whoami`.chomp
|
|
34
|
+
rescue StandardError
|
|
35
|
+
''
|
|
36
|
+
end
|
|
37
|
+
)
|
|
38
|
+
@user.present? ? "user: #{@user}" : ''
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def body_with_caller
|
|
42
|
+
"#{body}\n#{call_stack_messages}\n"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def notify_inline
|
|
46
|
+
notifier.inline_notify(notification_data)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def notify_out_of_channel
|
|
50
|
+
notifier.out_of_channel_notify(notification_data)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def short_notice
|
|
54
|
+
parts = []
|
|
55
|
+
parts << whoami.presence unless Bullet.skip_user_in_notification
|
|
56
|
+
parts << url
|
|
57
|
+
parts << title
|
|
58
|
+
parts << body
|
|
59
|
+
|
|
60
|
+
parts.compact.join(' ')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def notification_data
|
|
64
|
+
hash = {}
|
|
65
|
+
hash[:user] = whoami unless Bullet.skip_user_in_notification
|
|
66
|
+
hash[:url] = url
|
|
67
|
+
hash[:title] = title
|
|
68
|
+
hash[:body] = body_with_caller
|
|
69
|
+
hash
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def eql?(other)
|
|
73
|
+
self.class == other.class && klazz_associations_str == other.klazz_associations_str
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def hash
|
|
77
|
+
[self.class, klazz_associations_str].hash
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
protected
|
|
81
|
+
|
|
82
|
+
def klazz_associations_str
|
|
83
|
+
" #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def associations_str
|
|
87
|
+
".includes(#{@associations.map { |a| a.to_s.to_sym }
|
|
88
|
+
.inspect})"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Notification
|
|
5
|
+
class NPlusOneQuery < Base
|
|
6
|
+
def initialize(callers, base_class, associations, path = nil)
|
|
7
|
+
super(base_class, associations, path)
|
|
8
|
+
|
|
9
|
+
@callers = callers
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def body
|
|
13
|
+
"#{klazz_associations_str}\n Add to your query: #{associations_str}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def title
|
|
17
|
+
"USE eager loading #{@path ? "in #{@path}" : 'detected'}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def notification_data
|
|
21
|
+
super.merge(backtrace: [])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def call_stack_messages
|
|
27
|
+
(['Call stack'] + @callers).join("\n ")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Notification
|
|
5
|
+
class UnusedEagerLoading < Base
|
|
6
|
+
def initialize(callers, base_class, associations, path = nil)
|
|
7
|
+
super(base_class, associations, path)
|
|
8
|
+
|
|
9
|
+
@callers = callers
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def body
|
|
13
|
+
"#{klazz_associations_str}\n Remove from your query: #{associations_str}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def title
|
|
17
|
+
"AVOID eager loading #{@path ? "in #{@path}" : 'detected'}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def notification_data
|
|
21
|
+
super.merge(backtrace: [])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def call_stack_messages
|
|
27
|
+
(['Call stack'] + @callers).join("\n ")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Notification
|
|
5
|
+
autoload :Base, 'bullet/notification/base'
|
|
6
|
+
autoload :UnusedEagerLoading, 'bullet/notification/unused_eager_loading'
|
|
7
|
+
autoload :NPlusOneQuery, 'bullet/notification/n_plus_one_query'
|
|
8
|
+
autoload :CounterCache, 'bullet/notification/counter_cache'
|
|
9
|
+
|
|
10
|
+
class UnoptimizedQueryError < StandardError
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module Bullet
|
|
6
|
+
class NotificationCollector
|
|
7
|
+
attr_reader :collection
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
reset
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def reset
|
|
14
|
+
@collection = Set.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add(value)
|
|
18
|
+
@collection << value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def notifications_present?
|
|
22
|
+
!@collection.empty?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack/request'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'cgi'
|
|
6
|
+
|
|
7
|
+
module Bullet
|
|
8
|
+
class Rack
|
|
9
|
+
include Dependency
|
|
10
|
+
|
|
11
|
+
NONCE_MATCHER = /(script|style)-src .*'nonce-(?<nonce>[A-Za-z0-9+\/]+={0,2})'/
|
|
12
|
+
|
|
13
|
+
def initialize(app)
|
|
14
|
+
@app = app
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(env)
|
|
18
|
+
return @app.call(env) unless Bullet.enable?
|
|
19
|
+
|
|
20
|
+
Bullet.start_request
|
|
21
|
+
status, headers, response = @app.call(env)
|
|
22
|
+
|
|
23
|
+
response_body = nil
|
|
24
|
+
|
|
25
|
+
if Bullet.notification? || Bullet.always_append_html_body
|
|
26
|
+
request = ::Rack::Request.new(env)
|
|
27
|
+
if Bullet.inject_into_page? && !skip_html_injection?(request) && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
|
28
|
+
if html_request?(headers, response)
|
|
29
|
+
response_body = response_body(response)
|
|
30
|
+
|
|
31
|
+
with_security_policy_nonce(headers) do |nonce|
|
|
32
|
+
response_body = append_to_html_body(response_body, footer_note(nonce)) if Bullet.add_footer
|
|
33
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
34
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
|
35
|
+
response_body = append_to_html_body(response_body, xhr_script(nonce))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
headers['Content-Length'] = response_body.bytesize.to_s
|
|
40
|
+
elsif !Bullet.skip_http_headers
|
|
41
|
+
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
|
42
|
+
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
Bullet.perform_out_of_channel_notifications(env)
|
|
46
|
+
end
|
|
47
|
+
[status, headers, response_body ? [response_body] : response]
|
|
48
|
+
ensure
|
|
49
|
+
Bullet.end_request
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# fix issue if response's body is a Proc
|
|
53
|
+
def empty?(response)
|
|
54
|
+
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
|
55
|
+
# those should not happen if the status is 200
|
|
56
|
+
return true if !response.respond_to?(:body) && !response.respond_to?(:first)
|
|
57
|
+
|
|
58
|
+
body = response_body(response)
|
|
59
|
+
body.nil? || body.empty?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def append_to_html_body(response_body, content)
|
|
63
|
+
body = response_body.dup
|
|
64
|
+
content = content.html_safe if content.respond_to?(:html_safe)
|
|
65
|
+
if body.include?('</body>')
|
|
66
|
+
position = body.rindex('</body>')
|
|
67
|
+
body.insert(position, content)
|
|
68
|
+
else
|
|
69
|
+
body << content
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def footer_note(nonce = nil)
|
|
74
|
+
%(<details id="bullet-footer" data-is-bullet-footer><summary>Bullet Warnings</summary><div>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message(nonce)}</div>#{footer_style(nonce)}</details>)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Make footer styles work with ContentSecurityPolicy style-src as self
|
|
78
|
+
def footer_style(nonce = nil)
|
|
79
|
+
css = <<~CSS
|
|
80
|
+
details#bullet-footer {cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;}
|
|
81
|
+
details#bullet-footer summary {font-weight: 600; padding: 2px 8px;}
|
|
82
|
+
details#bullet-footer div {padding: 8px; border-top: 1px solid #9b1c1c;}
|
|
83
|
+
CSS
|
|
84
|
+
if nonce
|
|
85
|
+
%(<style type="text/css" nonce="#{nonce}">#{css}</style>)
|
|
86
|
+
else
|
|
87
|
+
%(<style type="text/css">#{css}</style>)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def set_header(headers, header_name, header_array)
|
|
92
|
+
# Many proxy applications such as Nginx and AWS ELB limit
|
|
93
|
+
# the size a header to 8KB, so truncate the list of reports to
|
|
94
|
+
# be under that limit
|
|
95
|
+
header_array.pop while JSON.generate(header_array).length > 8 * 1024
|
|
96
|
+
headers[header_name] = JSON.generate(header_array)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def skip_html_injection?(request)
|
|
100
|
+
query_string = request.env['QUERY_STRING']
|
|
101
|
+
return false if query_string.nil? || query_string.empty?
|
|
102
|
+
|
|
103
|
+
params = simple_parse_query_string(query_string)
|
|
104
|
+
params['skip_html_injection'] == 'true'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Simple query string parser
|
|
108
|
+
def simple_parse_query_string(query_string)
|
|
109
|
+
params = {}
|
|
110
|
+
query_string.split('&').each do |pair|
|
|
111
|
+
key, value = pair.split('=', 2).map { |s| CGI.unescape(s) }
|
|
112
|
+
params[key] = value if key && !key.empty?
|
|
113
|
+
end
|
|
114
|
+
params
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def file?(headers)
|
|
118
|
+
headers['Content-Transfer-Encoding'] == 'binary' || headers['Content-Disposition']
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def sse?(headers)
|
|
122
|
+
headers['Content-Type'] == 'text/event-stream'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def html_request?(headers, response)
|
|
126
|
+
headers['Content-Type']&.include?('text/html')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def response_body(response)
|
|
130
|
+
if response.respond_to?(:body)
|
|
131
|
+
Array === response.body ? response.body.first : response.body
|
|
132
|
+
elsif response.respond_to?(:first)
|
|
133
|
+
response.first
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def footer_console_message(nonce = nil)
|
|
140
|
+
if Bullet.console_enabled?
|
|
141
|
+
footer = %(<br/><span id="console-message">See 'Uniform Notifier' in JS Console for Stacktrace</span>)
|
|
142
|
+
css = "details#bullet-footer #console-message {font-style: italic;}"
|
|
143
|
+
style =
|
|
144
|
+
if nonce
|
|
145
|
+
%(<style type="text/css" nonce="#{nonce}">#{css}</style>)
|
|
146
|
+
else
|
|
147
|
+
%(<style type="text/css">#{css}</style>)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
footer + style
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Make footer work for XHR requests by appending data to the footer
|
|
155
|
+
def xhr_script(nonce = nil)
|
|
156
|
+
script = File.read("#{__dir__}/bullet_xhr.js")
|
|
157
|
+
|
|
158
|
+
if nonce
|
|
159
|
+
"<script type='text/javascript' nonce='#{nonce}'>#{script}</script>"
|
|
160
|
+
else
|
|
161
|
+
"<script type='text/javascript'>#{script}</script>"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def with_security_policy_nonce(headers)
|
|
166
|
+
csp = headers['Content-Security-Policy'] || headers['Content-Security-Policy-Report-Only'] || ''
|
|
167
|
+
matched = csp.match(NONCE_MATCHER)
|
|
168
|
+
nonce = matched[:nonce] if matched
|
|
169
|
+
|
|
170
|
+
if nonce
|
|
171
|
+
console_enabled = UniformNotifier.console
|
|
172
|
+
alert_enabled = UniformNotifier.alert
|
|
173
|
+
|
|
174
|
+
UniformNotifier.console = { attributes: { nonce: nonce } } if console_enabled
|
|
175
|
+
UniformNotifier.alert = { attributes: { nonce: nonce } } if alert_enabled
|
|
176
|
+
|
|
177
|
+
yield nonce
|
|
178
|
+
|
|
179
|
+
UniformNotifier.console = console_enabled
|
|
180
|
+
UniformNotifier.alert = alert_enabled
|
|
181
|
+
else
|
|
182
|
+
yield
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Registry
|
|
5
|
+
class Association < Base
|
|
6
|
+
def merge(base, associations)
|
|
7
|
+
@registry.merge!(base => associations)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def similarly_associated(base, associations)
|
|
11
|
+
@registry.select { |key, value| key.include?(base) && value == associations }
|
|
12
|
+
.collect(&:first).flatten
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Registry
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :registry
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@registry = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def [](key)
|
|
13
|
+
@registry[key]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def each(&block)
|
|
17
|
+
@registry.each(&block)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delete(base)
|
|
21
|
+
@registry.delete(base)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def select(*args, &block)
|
|
25
|
+
@registry.select(*args, &block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add(key, value)
|
|
29
|
+
@registry[key] ||= Set.new
|
|
30
|
+
if value.is_a? Array
|
|
31
|
+
@registry[key] += value
|
|
32
|
+
else
|
|
33
|
+
@registry[key] << value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def include?(key, value)
|
|
38
|
+
key?(key) && @registry[key].include?(value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def key?(key)
|
|
42
|
+
@registry.key?(key)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Registry
|
|
5
|
+
class CallStack < Base
|
|
6
|
+
# remembers found association backtrace
|
|
7
|
+
# if backtrace is provided, it will be used and override any existing value
|
|
8
|
+
def add(key, backtrace = nil)
|
|
9
|
+
if backtrace
|
|
10
|
+
@registry[key] = backtrace
|
|
11
|
+
else
|
|
12
|
+
@registry[key] ||= Thread.current.backtrace
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
using Bullet::Ext::Object
|
|
4
|
+
using Bullet::Ext::String
|
|
5
|
+
|
|
6
|
+
module Bullet
|
|
7
|
+
module Registry
|
|
8
|
+
class Object < Base
|
|
9
|
+
def add(bullet_key)
|
|
10
|
+
super(bullet_key.bullet_class_name, bullet_key)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def include?(bullet_key)
|
|
14
|
+
super(bullet_key.bullet_class_name, bullet_key)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Registry
|
|
5
|
+
autoload :Base, 'bullet/registry/base'
|
|
6
|
+
autoload :Object, 'bullet/registry/object'
|
|
7
|
+
autoload :Association, 'bullet/registry/association'
|
|
8
|
+
autoload :CallStack, 'bullet/registry/call_stack'
|
|
9
|
+
end
|
|
10
|
+
end
|