drive 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bcf5e0361ac44acb57bedda1f651a948abd3ec6bc43cd214080556a142d4b72
4
- data.tar.gz: 8c0e23b04b48848ee631f8d08a8f2c97cf15263a4ac5c4c6f58c14654de6fe1a
3
+ metadata.gz: c3a8ca81a5230ba8bad7ae1d5f02c1dced9130c1e4f25068a079e41bb28e082d
4
+ data.tar.gz: 4094ecf0fedb81c77a8764c6aedbc191e89e99400b45369a20b006a514a08663
5
5
  SHA512:
6
- metadata.gz: 7a671f1e9f52a5a4422cbef14c40ab75d968be2bfe3423b9fccf3c225b6fd0227b5bfe8c301d9acd49036433b4e8efbf389a8b3068492f918aaff9b8139c15bd
7
- data.tar.gz: de0351f9c2748aa969ef3fb1e2fbbf027dec38d689b733911f366f4d6e5b1e1f87ec6c7beaf365fa1cd2ab24bbd72f8e7ff42563964e37ebbbabeefd71b780cb
6
+ metadata.gz: b5d1ace9067f6c0ff8f7cd433742e434373ec9f4a755e31a18e37c4acc875e703ea74ce374076ebe028cc919bc347df925df25e76de8802e645111ccb76b7fe2
7
+ data.tar.gz: 2e0fa021cee1892ee83e6c8e335a79794c4cc1abe144e9a562516451dd63a8b7c0e202a25b61b31eb33ff7ab4aa1c791d2ac0584f017e5ac409733dee516ef2e
data/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 0.3.0 - 2026-10-06
10
+
11
+ * [FEATURE] `RateLimited` no longer limits a search engine's own crawler
12
+
13
+ `Crawler.verified?(request)` believes a user agent naming Google's, Bing's or Apple's crawler
14
+ only where its address resolves to their domain and that host back to the address, and
15
+ remembers the answer for a day. Behind a proxy, `request.remote_ip` must be the visitor's:
16
+ list the proxy's ranges in `config.action_dispatch.trusted_proxies`.
17
+
9
18
  ## 0.2.0 - 2026-10-05
10
19
 
11
20
  * [BREAKING] `drive` is a new gem: a Rails engine for apps that mirror what a source publishes
data/README.md CHANGED
@@ -10,7 +10,10 @@ files, a monthly database — and tells another app exactly which rows changed.
10
10
  - **`Change`**: every row a run inserted, updated or deleted, with the row as it is now.
11
11
  - **`FeedsController`**: `GET /feed?after=ID&kind=TABLE`, the next 5,000 changes, as JSON.
12
12
  - **`AccessFile`**: stages a table of a Microsoft Access file, read with `mdb-export`.
13
- - **`RateLimited`**: 20 requests a minute and 200 a day per address, for public pages.
13
+ - **`RateLimited`**: 20 requests a minute and 200 a day per address, for public pages. A search
14
+ engine's own crawler has no limit.
15
+ - **`Crawler`**: whether a request comes from Google's, Bing's or Apple's crawler: its address
16
+ resolves to their domain and back, checked once a day per address.
14
17
 
15
18
  It needs PostgreSQL.
16
19
 
@@ -1,9 +1,10 @@
1
- # The limits of a page anybody may ask for, by the address asking: 20 a minute, 200 a day.
1
+ # The limits of a page anybody may ask for, by the address asking: 20 a minute, 200 a day. A search
2
+ # engine's own crawler has none, so it can list every page.
2
3
  module RateLimited
3
4
  extend ActiveSupport::Concern
4
5
 
5
6
  included do
6
- rate_limit to: 20, within: 1.minute, name: 'minute'
7
- rate_limit to: 200, within: 1.day, name: 'day'
7
+ rate_limit to: 20, within: 1.minute, name: 'minute', unless: -> { Crawler.verified? request }
8
+ rate_limit to: 200, within: 1.day, name: 'day', unless: -> { Crawler.verified? request }
8
9
  end
9
10
  end
@@ -0,0 +1,33 @@
1
+ require 'resolv'
2
+
3
+ # A search engine's own crawler, told apart from a client that only names one in its user agent:
4
+ # its address resolves to the search engine's domain, and that host back to the address, which is
5
+ # how Google and Bing say to check theirs.
6
+ module Crawler
7
+ # The user agents of the crawlers checked.
8
+ AGENTS = /Googlebot|Google-InspectionTool|Storebot-Google|GoogleOther|bingbot|Applebot/i
9
+
10
+ # The domains their addresses resolve to.
11
+ DOMAINS = %w[.googlebot.com .google.com .search.msn.com .applebot.apple.com].freeze
12
+
13
+ # @param request [ActionDispatch::Request] request a page was asked with.
14
+ # @return [Boolean] whether it came from a crawler, checked once a day for each address.
15
+ def self.verified?(request)
16
+ ip = request.remote_ip
17
+ request.user_agent.to_s.match?(AGENTS) &&
18
+ Rails.cache.fetch(['crawler', ip], expires_in: 1.day) { resolves? ip }
19
+ end
20
+
21
+ # @param ip [String] address a request came from.
22
+ # @return [Boolean] whether it names a crawler's host, and that host names it back.
23
+ def self.resolves?(ip)
24
+ dns = Resolv::DNS.new.tap { it.timeouts = 2 }
25
+ host = dns.getname(ip).to_s
26
+ DOMAINS.any? { host.end_with? it } && dns.getaddresses(host).map(&:to_s).include?(ip)
27
+ rescue Resolv::ResolvError
28
+ false
29
+ ensure
30
+ dns&.close
31
+ end
32
+ private_class_method :resolves?
33
+ end
data/lib/drive/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Drive
2
2
  # Version of the gem, read by the gemspec.
3
- VERSION = '0.2.0'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob
@@ -69,6 +69,7 @@ files:
69
69
  - app/models/change.rb
70
70
  - app/models/concerns/logged.rb
71
71
  - app/models/concerns/mirrored.rb
72
+ - app/models/crawler.rb
72
73
  - app/models/drive/current.rb
73
74
  - app/models/run.rb
74
75
  - db/migrate/20261005100000_create_runs_and_changes.rb