Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 193
- Log:
First stage commit of Typo 4.1, modified for the ROOL site.
Includes all local modifications but a final pass needs to be
made to delete any files left over from earlier Typo versions
that shouldn't be here anymore. See the 'tags' section of the
repository for a clean Typo 4.1 tree.Note that symlinks to shared files in the RISC OS Open theme
directory have been deliberately included this time around; I
decided that on balance it was better to leave them in as
placeholders, since unlike symlinks in app/views/shared, the
Typo theme structure is not a standard Rails concept.
- Author:
- rool
- Date:
- Wed Apr 04 18:51:02 +0100 2007
- Size:
- 3941 Bytes
1 | class SpamProtection |
2 | |
3 | IP_RBLS = [ 'opm.blitzed.us', 'bsb.empty.us' ] |
4 | HOST_RBLS = [ 'multi.surbl.org', 'bsb.empty.us' ] |
5 | SECOND_LEVEL = [ 'co', 'com', 'net', 'org', 'gov' ] |
6 | |
7 | attr_accessor :this_blog |
8 | |
9 | def initialize(a_blog) |
10 | self.this_blog = a_blog |
11 | end |
12 | |
13 | def article_closed?(record) |
14 | return false if this_blog.sp_article_auto_close.zero? or not record.new_record? |
15 | |
16 | if record.article.published_at.to_i < this_blog.sp_article_auto_close.days.ago.to_i |
17 | logger.info("[SP] Blocked interaction with #{record.article.title}") |
18 | return true |
19 | end |
20 | end |
21 | |
22 | def is_spam?(string) |
23 | return false unless this_blog.sp_global |
24 | return false if string.blank? |
25 | |
26 | reason = catch(:hit) do |
27 | case string |
28 | when Format::IP_ADDRESS: self.scan_ip(string) |
29 | when Format::HTTP_URI: self.scan_uris(string) rescue URI::InvalidURIError |
30 | else self.scan_text(string) |
31 | end |
32 | end |
33 | |
34 | if reason |
35 | logger.info("[SP] Hit: #{reason}") |
36 | return true |
37 | end |
38 | end |
39 | |
40 | protected |
41 | |
42 | def scan_ip(ip_address) |
43 | logger.info("[SP] Scanning IP #{ip_address}") |
44 | query_rbls(IP_RBLS, ip_address.split('.').reverse.join('.')) |
45 | end |
46 | |
47 | def scan_text(string) |
48 | uri_list = string.scan(/(http:\/\/[^\s"]+)/m).flatten |
49 | |
50 | check_uri_count(uri_list) |
51 | check_against_blacklist(string) |
52 | scan_uris(uri_list) |
53 | |
54 | return false |
55 | end |
56 | |
57 | def check_against_blacklist(text) |
58 | # Pattern scanning |
59 | BlacklistPattern.find(:all).each do |pattern| |
60 | logger.info("[SP] Scanning for #{pattern.class} #{pattern.pattern}") |
61 | |
62 | throw :hit, "#{pattern} matched" if pattern.matches?(text) |
63 | end |
64 | end |
65 | |
66 | def check_uri_count(uris) |
67 | limit = this_blog.sp_url_limit |
68 | return if limit.to_i.zero? |
69 | if uris.size > limit |
70 | throw :hit, "Hard URL Limit hit: #{uris.size} > #{limit}" |
71 | end |
72 | end |
73 | |
74 | def scan_uris(uris = []) |
75 | uris.each do |uri| |
76 | host = URI.parse(uri).host rescue next |
77 | return scan_ip(host) if host =~ Format::IP_ADDRESS |
78 | |
79 | host_parts = host.split('.').reverse |
80 | domain = Array.new |
81 | |
82 | # Check for two level TLD |
83 | (SECOND_LEVEL.include?(host_parts[1]) ? 3:2).times do |
84 | domain.unshift(host_parts.shift) |
85 | end |
86 | |
87 | logger.info("[SP] Scanning domain #{domain.join('.')}") |
88 | query_rbls(HOST_RBLS, host, domain.join('.')) |
89 | logger.info("[SP] Finished domain scan #{domain.join('.')}") |
90 | return false |
91 | end |
92 | end |
93 | |
94 | def query_rbls(rbls, *subdomains) |
95 | rbls.each do |rbl| |
96 | subdomains.uniq.each do |d| |
97 | begin |
98 | response = IPSocket.getaddress([d, rbl].join('.')) |
99 | if response =~ /^127\.0\.0\./ |
100 | throw :hit, |
101 | "#{rbl} positively resolved subdomain #{d} => #{response}" |
102 | end |
103 | rescue SocketError |
104 | # NXDOMAIN response => negative: d is not in RBL |
105 | end |
106 | end |
107 | end |
108 | return false |
109 | end |
110 | |
111 | def logger |
112 | @logger ||= RAILS_DEFAULT_LOGGER || Logger.new(STDOUT) |
113 | end |
114 | end |
115 | |
116 | module ActiveRecord |
117 | module Validations |
118 | module ClassMethods |
119 | def validates_against_spamdb(*attr_names) |
120 | configuration = { :message => "blocked by SpamProtection" } |
121 | configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) |
122 | |
123 | validates_each(attr_names, configuration) do |record, attr_name, value| |
124 | record.errors.add(attr_name, configuration[:message]) if SpamProtection.new(record.blog).is_spam?(value) |
125 | end |
126 | end |
127 | def validates_age_of(*attr_names) |
128 | configuration = { :on => :create, :message => "points to an item that is no longer available for interaction"} |
129 | configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) |
130 | |
131 | validates_each(attr_names, configuration) do |record, attr_name, value| |
132 | next unless value.to_i > 0 |
133 | record.errors.add(attr_name, configuration[:message]) if SpamProtection.new(record.blog).article_closed?(record) |
134 | end |
135 | end |
136 | end |
137 | end |
138 | end |