mikehale-akismet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 1
3
+ :major: 0
4
+ :minor: 0
data/lib/akismet.rb ADDED
@@ -0,0 +1,64 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ class Akismet
5
+ USER_AGENT = "Akismet-rb/1.0 | Akismet/1.11"
6
+
7
+ def initialize(key, url)
8
+ @key = key
9
+ @url = url
10
+ end
11
+
12
+ def verify_key
13
+ response = Net::HTTP.start('rest.akismet.com', 80) do |http|
14
+ http.post('/1.1/verify-key', post_data(:key => @key, :blog => @url), {'User-Agent' => USER_AGENT})
15
+ end
16
+
17
+ case response.body
18
+ when "invalid"
19
+ raise Akismet::VerifyException, response.to_hash["x-akismet-debug-help"], caller
20
+ when "valid"
21
+ true
22
+ end
23
+ end
24
+
25
+ def submit_spam(args)
26
+ call_akismet('submit-spam', args)
27
+ end
28
+
29
+ def submit_ham(args)
30
+ call_akismet('submit-ham', args)
31
+ end
32
+
33
+ def spam?(args)
34
+ call_akismet('comment-check', args)
35
+ end
36
+
37
+ def ham?(args)
38
+ !spam?(args)
39
+ end
40
+
41
+ def call_akismet(method, args)
42
+ args.update(:blog => @url)
43
+
44
+ response = Net::HTTP.start("#{@key}.rest.akismet.com", 80) do |http|
45
+ http.post("/1.1/#{method}", post_data(args), {'User-Agent' => USER_AGENT})
46
+ end
47
+
48
+ case response.body
49
+ when "true"
50
+ true
51
+ when "false"
52
+ false
53
+ end
54
+ end
55
+
56
+ def post_data(hash)
57
+ hash.inject([]) do |memo, hash|
58
+ k, v = hash
59
+ memo << "#{k}=#{URI.escape(v)}"
60
+ end.join('&')
61
+ end
62
+
63
+ class VerifyException < Exception; end
64
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Akismet" do
4
+ include SpecHttp
5
+
6
+ def params
7
+ {
8
+ :user_ip => "1.2.3.4",
9
+ :referrer => "http://othersite.com",
10
+ :permalink => "http://example.com/post",
11
+ :comment_type => "comment",
12
+ :comment_author => "joe smith",
13
+ :comment_author_email => "joe@smith.com",
14
+ :comment_author_url => "blog.smith.com"
15
+ }
16
+ end
17
+
18
+ before do
19
+ @akismet = Akismet.new('thekey', 'http://example.com')
20
+ app = lambda do |env|
21
+ spam = env["rack.input"].include?('viagra')
22
+ [200, {}, [spam ? "true" : "false"]]
23
+ end
24
+ map Rack::URLMap.new("http://thekey.rest.akismet.com/1.1/comment-check" => app)
25
+ end
26
+
27
+ it "should verify the key" do
28
+ map Rack::URLMap.new("http://rest.akismet.com/" => lambda { |env| [200, {}, ["valid"]]})
29
+
30
+ @akismet.verify_key.should == true
31
+ request.post?.should == true
32
+ request.env['HTTP_USER_AGENT'].should == "Akismet-rb/1.0 | Akismet/1.11"
33
+ request.body.should include('http://example.com')
34
+ response.status.should == 200
35
+ end
36
+
37
+ it "should not verify an invalid key" do
38
+ map Rack::URLMap.new("http://rest.akismet.com/" => lambda { |env| [200, {'x-akismet-debug-help' => 'sorry!'}, ["invalid"]]})
39
+ lambda {@akismet.verify_key}.should raise_error Akismet::VerifyException
40
+ response['x-akismet-debug-help'].should == 'sorry!'
41
+ end
42
+
43
+ it "should detect spam" do
44
+ @akismet.spam?(params.update(:comment_content => "viagra-test-123")).should == true
45
+ request.env.has_key?('HTTP_USER_AGENT').should == true
46
+ request.body.should include("blog=http://example.com")
47
+ request.body.should include("user_ip=1.2.3.4")
48
+ request.body.should include("referrer=http://othersite.com")
49
+ request.body.should include("permalink=http://example.com/post")
50
+ request.body.should include("comment_type=comment")
51
+ request.body.should include("comment_author=joe%20smith")
52
+ request.body.should include("comment_author_email=joe@smith.com")
53
+ request.body.should include("comment_author_url=blog.smith.com")
54
+ request.body.should include("comment_content=viagra-test-123")
55
+ end
56
+
57
+ it "should detect ham" do
58
+ @akismet.ham?(params.update(:comment_content => "not spam")).should == true
59
+ end
60
+
61
+ it "should submit spam" do
62
+ map Rack::URLMap.new("http://thekey.rest.akismet.com/1.1/submit-spam" => lambda { |env| [200, {}, "true"]})
63
+ @akismet.submit_spam(params.update(:comment_content => "this-is-spam"))
64
+ request.script_name.should == "/1.1/submit-spam"
65
+
66
+ request.env.has_key?('HTTP_USER_AGENT').should == true
67
+ request.body.should include("blog=http://example.com")
68
+ request.body.should include("user_ip=1.2.3.4")
69
+ request.body.should include("referrer=http://othersite.com")
70
+ request.body.should include("permalink=http://example.com/post")
71
+ request.body.should include("comment_type=comment")
72
+ request.body.should include("comment_author=joe%20smith")
73
+ request.body.should include("comment_author_email=joe@smith.com")
74
+ request.body.should include("comment_author_url=blog.smith.com")
75
+ request.body.should include("comment_content=this-is-spam")
76
+ end
77
+
78
+ it "should submit ham" do
79
+ map Rack::URLMap.new("http://thekey.rest.akismet.com/1.1/submit-ham" => lambda { |env| [200, {}, "true"]})
80
+ @akismet.submit_ham(params.update(:comment_content => "this-is-ham"))
81
+ request.script_name.should == "/1.1/submit-ham"
82
+ end
83
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,26 @@
1
+ require 'spec'
2
+ require 'rr'
3
+ require 'rack/urlmap'
4
+ require File.dirname(__FILE__) + '/spec_http'
5
+ require 'ruby-debug'
6
+
7
+ class MethodSpy
8
+ def initialize(delegate, &block)
9
+ @delegate = delegate
10
+ @filter = block
11
+ end
12
+
13
+ def method_missing(symbol, *args, &block)
14
+ result = @delegate.send(symbol, *args, &block)
15
+ p [symbol, args, result, block]
16
+ result
17
+ end
18
+ end
19
+
20
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
21
+
22
+ require 'akismet'
23
+
24
+ Spec::Runner.configure do |config|
25
+ config.mock_with :rr
26
+ end
data/spec/spec_http.rb ADDED
@@ -0,0 +1,164 @@
1
+ require 'uri'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+ require 'rack/utils'
5
+
6
+ module SpecHttp
7
+ def map(map)
8
+ @socket = SocketInterceptor.new(map)
9
+ stub(TCPSocket).open(anything, numeric) { @socket }
10
+ end
11
+
12
+ def request
13
+ Rack::Request.new(@socket.env)
14
+ end
15
+
16
+ def response
17
+ @socket.response
18
+ end
19
+ end
20
+
21
+ class SocketInterceptor < StringIO
22
+ attr_reader :env, :response
23
+
24
+ def initialize(map)
25
+ @map = map
26
+ super()
27
+ end
28
+
29
+ def write(string)
30
+ raise "Already responded" if self.string.size > 0
31
+ @written = '' unless @written
32
+ @written << string
33
+ string.size
34
+ end
35
+
36
+ # this depends on Net::HTTP calling socket.sysread after it has finished all writes
37
+ alias :orig_sysread :sysread
38
+ def sysread(size)
39
+ set_string unless self.string.size > 0
40
+ orig_sysread(size)
41
+ end
42
+
43
+ def set_string
44
+ response_io = StringIO.new
45
+ @env = Rack::RequestStringParser.env(@written)
46
+ Rack::Handler::StringIO.run(app(@env), @env, response_io)
47
+ @response = Rack::Handler::StringIO.response
48
+ self.string = response_io.string
49
+ end
50
+
51
+ def app(env)
52
+ app_to_use = @map if !@map.respond_to? :mapping
53
+ unless app_to_use
54
+ path = env["PATH_INFO"].to_s.squeeze("/")
55
+ hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
56
+ @map.mapping.each { |host, location, app|
57
+ next unless (hHost == host || sName == host \
58
+ || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
59
+ next unless location == path[0, location.size]
60
+ next unless path[location.size] == nil || path[location.size] == ?/
61
+
62
+ env["SCRIPT_NAME"] += location
63
+ env["PATH_INFO"] = path[location.size..-1]
64
+ app_to_use = app
65
+ }
66
+ raise "No application associated with #{env['rack.url_scheme']}://#{hHost}#{path}" unless app_to_use
67
+ end
68
+ app_to_use
69
+ end
70
+ end
71
+
72
+ module Rack
73
+ class URLMap
74
+ attr_reader :mapping
75
+ end
76
+
77
+ class RequestStringParser
78
+ def self.env_key(key)
79
+ key = key.split('-').join('_').upcase
80
+ key = "HTTP_#{key}" unless key =~ /content/i
81
+ key
82
+ end
83
+
84
+ def self.env(input)
85
+ lines = input.split("\r\n")
86
+
87
+ # find blank line which seperates the headers from the body
88
+ index_of_blank = nil
89
+ lines.each_with_index{|e,i|
90
+ index_of_blank = i if e == ""
91
+ }
92
+
93
+ type, uri = lines.first.split(/\s+/)
94
+
95
+ if index_of_blank
96
+ headers = lines[1..index_of_blank]
97
+ body = lines[(index_of_blank + 1)..-1].first
98
+ else
99
+ headers = lines[1..-1]
100
+ end
101
+
102
+ headers = headers.inject({}){|h,e|
103
+ k,v = e.split(/:\s+/)
104
+ h.merge! env_key(k) => v if k
105
+ h
106
+ }
107
+
108
+ uri = URI(uri)
109
+ env = headers.dup
110
+ env["SERVER_NAME"] = uri.host
111
+ env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
112
+ env["QUERY_STRING"] = uri.query.to_s
113
+ env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
114
+ env["SCRIPT_NAME"] = ""
115
+ env["REQUEST_METHOD"] = type
116
+ env["rack.url_scheme"] = uri.scheme || "http"
117
+ env["rack.input"] = body
118
+ env
119
+ end
120
+ end
121
+
122
+ module Handler
123
+ class StringIO
124
+ def self.response
125
+ @@response
126
+ end
127
+
128
+ def self.run(app, env={}, output=StringIO.new)
129
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
130
+ env["QUERY_STRING"] ||= ""
131
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
132
+ env["REQUEST_PATH"] ||= "/"
133
+
134
+ status, headers, body = app.call(env)
135
+ @@response = Rack::Response.new(body, status, headers)
136
+ begin
137
+ send_headers output, status, headers
138
+ send_body output, body
139
+ output.rewind
140
+ ensure
141
+ body.close if body.respond_to? :close
142
+ end
143
+ end
144
+
145
+ def self.send_headers(output, status, headers)
146
+ output.print "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}\r\n"
147
+ headers.each { |k, vs|
148
+ vs.each { |v|
149
+ output.print "#{k}: #{v}\r\n"
150
+ }
151
+ }
152
+ output.print "\r\n"
153
+ output.flush
154
+ end
155
+
156
+ def self.send_body(output, body)
157
+ body.each { |part|
158
+ output.print part
159
+ output.flush
160
+ }
161
+ end
162
+ end
163
+ end
164
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mikehale-akismet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Hale
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-07 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: TODO
17
+ email: mikehale@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - lib/akismet.rb
27
+ - spec/akismet_spec.rb
28
+ - spec/spec.opts
29
+ - spec/spec_helper.rb
30
+ - spec/spec_http.rb
31
+ has_rdoc: true
32
+ homepage: http://github.com/mikehale/akismet
33
+ post_install_message:
34
+ rdoc_options:
35
+ - --inline-source
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: TODO
58
+ test_files: []
59
+