em-spamc 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'eventmachine'
4
+
5
+ group :development do
6
+ gem 'bundler'
7
+ gem 'jeweler', '~> 1.8.4'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Scott Tadman, The Working Group Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,8 @@
1
+ #em-spamc
2
+
3
+ SpamAssassin Connection for EventMachine
4
+
5
+ ##Copyright
6
+
7
+ Copyright (c) 2013 Scott Tadman, Stephan Leroux, The Working Group Inc.
8
+ See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'rake'
15
+ require 'jeweler'
16
+
17
+ Jeweler::Tasks.new do |gem|
18
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
19
+ gem.name = "em-spamc"
20
+ gem.homepage = "http://github.com/twg/em-spamc"
21
+ gem.license = "MIT"
22
+ gem.summary = %Q{SpamAssassin Connection for EventMachine}
23
+ gem.description = %Q{Implementation of the "spamc" protocol for EventMachine}
24
+ gem.email = "github@tadman.ca"
25
+ gem.authors = [ "Scott Tadman", "Stephan Leroux" ]
26
+ # dependencies defined in Gemfile
27
+ end
28
+
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+
33
+ Rake::TestTask.new(:test) do |test|
34
+ test.libs << 'lib' << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/em-spamc.gemspec ADDED
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "em-spamc"
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Scott Tadman", "Stephan Leroux"]
12
+ s.date = "2013-04-17"
13
+ s.description = "Implementation of the \"spamc\" protocol for EventMachine"
14
+ s.email = "github@tadman.ca"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "em-spamc.gemspec",
27
+ "lib/em-spamc.rb",
28
+ "lib/em_spamc.rb",
29
+ "lib/em_spamc/connection.rb",
30
+ "lib/em_spamc/header_parser.rb",
31
+ "lib/em_spamc/report_parser.rb",
32
+ "lib/em_spamc/result.rb",
33
+ "test/config/spamassassin.yml",
34
+ "test/config/spamassassin.yml.example",
35
+ "test/examples/messages/sample.txt",
36
+ "test/examples/messages/zero-score.txt",
37
+ "test/examples/reports/spam-report.txt",
38
+ "test/examples/reports/zero-report.txt",
39
+ "test/examples/sample.txt",
40
+ "test/helper.rb",
41
+ "test/unit/test_em_spamc.rb",
42
+ "test/unit/test_em_spamc_connection.rb",
43
+ "test/unit/test_em_spamc_header_parser.rb",
44
+ "test/unit/test_em_spamc_report_parser.rb",
45
+ "test/unit/test_em_spamc_result.rb"
46
+ ]
47
+ s.homepage = "http://github.com/twg/em-spamc"
48
+ s.licenses = ["MIT"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = "1.8.25"
51
+ s.summary = "SpamAssassin Connection for EventMachine"
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
58
+ s.add_development_dependency(%q<bundler>, [">= 0"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
60
+ else
61
+ s.add_dependency(%q<eventmachine>, [">= 0"])
62
+ s.add_dependency(%q<bundler>, [">= 0"])
63
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
64
+ end
65
+ else
66
+ s.add_dependency(%q<eventmachine>, [">= 0"])
67
+ s.add_dependency(%q<bundler>, [">= 0"])
68
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
69
+ end
70
+ end
71
+
data/lib/em-spamc.rb ADDED
@@ -0,0 +1 @@
1
+ require 'em_spamc'
data/lib/em_spamc.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'fiber'
2
+
3
+ module EmSpamc
4
+ autoload(:Connection, 'em_spamc/connection')
5
+ autoload(:Result, 'em_spamc/result')
6
+ autoload(:ReportParser, 'em_spamc/report_parser')
7
+ autoload(:HeaderParser, 'em_spamc/header_parser')
8
+ end
@@ -0,0 +1,105 @@
1
+ require 'fiber'
2
+ require 'eventmachine'
3
+
4
+ class EmSpamc::Connection < EventMachine::Connection
5
+ # == Constants ============================================================
6
+
7
+ PROTO_VERSION = 'SPAMC/1.2'.freeze
8
+
9
+ DEFAULT_OPTIONS = {
10
+ :host => 'localhost',
11
+ :port => 783
12
+ }.freeze
13
+
14
+ # == Properties ===========================================================
15
+
16
+ attr_reader :error
17
+
18
+ # == Class Methods ========================================================
19
+
20
+ def self.check(message, options = nil)
21
+ request('CHECK', message, options)
22
+ end
23
+
24
+ def self.symbols(message, options = nil)
25
+ request('SYMBOLS', message, options)
26
+ end
27
+
28
+ def self.report(message, options = nil)
29
+ request('REPORT', message, options)
30
+ end
31
+
32
+ def self.ping(options = nil)
33
+ request('PING', nil, options)
34
+ end
35
+
36
+ def self.request(command, message, options = nil)
37
+ if (options)
38
+ options = DEFAULT_OPTIONS.merge(options)
39
+ else
40
+ options = DEFAULT_OPTIONS
41
+ end
42
+
43
+ EventMachine.connect(
44
+ options[:host],
45
+ options[:port],
46
+ self,
47
+ [
48
+ Fiber.current,
49
+ command,
50
+ message
51
+ ]
52
+ )
53
+
54
+ Fiber.yield
55
+ end
56
+
57
+ # == Instance Methods =====================================================
58
+
59
+ def initialize(options)
60
+ @fiber = options[0]
61
+ @command = options[1]
62
+ @message = options[2] && (options[2].gsub(/\r?\n/, "\r\n") + "\r\n")
63
+ end
64
+
65
+ # EventMachine hook that's engaged after the client is initialized.
66
+ def post_init
67
+ if (error?)
68
+ @fiber.resume
69
+ else
70
+ send_data("#{@command} #{PROTO_VERSION}\r\n")
71
+
72
+ if (@message)
73
+ send_data("Content-length: #{@message.bytesize}\r\n")
74
+ send_data("\r\n")
75
+ send_data(@message)
76
+ else
77
+ send_data("\r\n")
78
+ end
79
+ end
80
+ end
81
+
82
+ def receive_data(data)
83
+ @data ||= ''
84
+ @data << data
85
+ end
86
+
87
+ def unbind
88
+ result = EmSpamc::Result.new
89
+
90
+ if (@data)
91
+ result.headers = EmSpamc::HeaderParser.parse(@data)
92
+
93
+ case (@command)
94
+ when 'REPORT'
95
+ result.report = EmSpamc::ReportParser.parse(@data)
96
+ end
97
+ end
98
+
99
+ @fiber.resume(result)
100
+ end
101
+
102
+ def error?
103
+ !!@error
104
+ end
105
+ end
@@ -0,0 +1,27 @@
1
+ class EmSpamc::HeaderParser
2
+ def self.parse(data)
3
+ lines = data.split(/\r?\n/)
4
+ headers = { }
5
+
6
+ lines.each do |line|
7
+ case (line)
8
+ when /^SPAMD\/(\d+\.\d+) (.*)/
9
+ code, message = $2.split(/\s+/)
10
+
11
+ headers[:version] = $1
12
+ headers[:code] = code.match(/\d/) ? code.to_i : code
13
+ headers[:message] = message
14
+ when /^(\S+): (.*)\s;\s([0-9]*.[0-9]*)/
15
+ headers[:spam] = $2 == 'True'
16
+ headers[:score] = $3.to_f
17
+ end
18
+ end
19
+
20
+ # Special case: if we only have one header line back, means score of 0
21
+ if (lines.length == 1)
22
+ headers.merge!(:score => 0.0, :spam => false)
23
+ end
24
+
25
+ headers
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ class EmSpamc::ReportParser
2
+ LINE_REGEXP = /-$/
3
+ RULE_REGEXP = /[0-9]*[.][0-9]\s\w*\s/
4
+
5
+ def self.parse(data)
6
+ split_data = data.split(LINE_REGEXP)
7
+
8
+ # Only status line is back - no report returned, no rules
9
+ if (split_data.length == 1)
10
+ return [ ]
11
+ end
12
+
13
+ last_part = split_data[1].sub(/^[\n\r]./,'').chomp.chomp
14
+
15
+ points_rules = last_part.gsub(RULE_REGEXP).collect do |sub|
16
+ sub.chomp(' ')
17
+ end
18
+
19
+ rule_texts = last_part.split(RULE_REGEXP).collect do |text|
20
+ text.delete("\n").squeeze.chomp(' ').sub(/^\s/, '')
21
+ end
22
+
23
+ rules = [ ]
24
+
25
+ points_rules.each_with_index do |points_rule, i|
26
+ split = points_rule.split(' ')
27
+
28
+ rules << {
29
+ :pts => split[0].to_f,
30
+ :rule => split[1],
31
+ :text => rule_texts[i + 1]
32
+ }
33
+ end
34
+
35
+ rules
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ class EmSpamc::Result
2
+ # == Constants ============================================================
3
+
4
+ ATTRIBUTES = [
5
+ :headers,
6
+ :threshold,
7
+ :tags,
8
+ :report
9
+ ].freeze
10
+
11
+ # == Instance Methods =====================================================
12
+
13
+ def initialize(attributes = nil)
14
+ @attributes = attributes || { }
15
+ end
16
+
17
+ ATTRIBUTES.each do |name|
18
+ define_method(name) do
19
+ @attributes[name]
20
+ end
21
+
22
+ define_method(:"#{name}=") do |value|
23
+ @attributes[name] = value
24
+ end
25
+ end
26
+
27
+ def spam?
28
+ headers and headers[:spam]
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ #host: 127.0.0.1
2
+ #port: 9000
3
+ host: scan1.blue.postageapp.com
4
+ port: 49000
@@ -0,0 +1,2 @@
1
+ host: 127.0.0.1
2
+ port: 783
@@ -0,0 +1,5 @@
1
+ From: Example Person <example@example.com>
2
+ To: Someone <someone@example.net>
3
+ Subject: Harmless Example Message
4
+
5
+ This is not a very exciting message, but here it is.
@@ -0,0 +1,23 @@
1
+ Delivered-To: test@test.com
2
+ Received: by 10.180.189.141 with SMTP id gi13csp63398wic;
3
+ Thu, 14 Mar 2013 08:46:03 -0700 (PDT)
4
+ Return-Path: <sender@testapp.com>
5
+ Received: from outbound6.mailer.testapp.com (outbound6.mailer.testapp.com. [199.87.247.245])
6
+ by mx.google.com with ESMTPS id ep2si2060874qeb.27.2013.03.14.08.46.02
7
+ (version=TLSv1 cipher=RC4-SHA bits=128/128);
8
+ Thu, 14 Mar 2013 08:46:03 -0700 (PDT)
9
+ Received-SPF: pass (google.com: domain of sender@testapp.com designates 199.87.247.245 as permitted sender) client-ip=199.87.247.245;
10
+ Authentication-Results: mx.google.com;
11
+ spf=pass (google.com: domain of sender@testapp.com designates 199.87.247.245 as permitted sender) smtp.mail=sender@testapp.com
12
+ Date: Thu, 14 Mar 2013 15:46:02 +0000
13
+ To: person@test.com
14
+ Message-ID: <17ee1aab2074416831081825744deffb026f0dfe@outbound6.mailer.testapp.com>
15
+ Subject: Test Message RECIPIENT SUBJECT person@test.com
16
+ Mime-Version: 1.0
17
+ Content-Type: text/plain
18
+ Content-Transfer-Encoding: 7bit
19
+ Auto-Submitted: auto-generated
20
+ From: sender@testapp.com
21
+
22
+ Hey!
23
+
@@ -0,0 +1,27 @@
1
+ SPAMD/1.1 0 EX_OK
2
+ Spam: True ; 18.1 / 5.0
3
+
4
+ Spam detection software, running on the system "scan1.blue.postageapp.com", has
5
+ identified this incoming email as possible spam. The original message
6
+ has been attached to this so you can view it (if it isn't spam) or label
7
+ similar future email. If you have any questions, see
8
+ the administrator of that system for details.
9
+
10
+ Content preview: I found a way to "get even" with the financial market! I've
11
+ stumbled to this page: http://advertismenmore.com/ This dude is just crazy,
12
+ he will teach you how you can make up to $87 every minute! No string attached
13
+ and zero risk for you! and the best fact, its 100% free [...]
14
+
15
+ Content analysis details: (18.1 points, 5.0 required)
16
+
17
+ pts rule name description
18
+ ---- ---------------------- --------------------------------------------------
19
+ 3.6 RCVD_IN_PBL RBL: Received via a relay in Spamhaus PBL
20
+ [91.115.88.37 listed in zen.spamhaus.org]
21
+ 0.7 RCVD_IN_XBL RBL: Received via a relay in Spamhaus XBL
22
+ 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL
23
+ was blocked. See
24
+ http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block
25
+ for more information.
26
+ [91.115.88.37 listed in list.dnswl.org]
27
+ 1.6 RCVD_IN_BRBL_LASTEXT RBL: RCVD_IN_BRBL_LASTEXT
@@ -0,0 +1 @@
1
+ SPAMD/1.1 0 EX_OK
@@ -0,0 +1,5 @@
1
+ From: Example Person <example@example.com>
2
+ To: Someone <someone@example.net>
3
+ Subject: Harmless Example Message
4
+
5
+ This is not a very exciting message, but here it is.
data/test/helper.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'yaml'
13
+ require 'test/unit'
14
+
15
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
16
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
17
+
18
+ require 'em-spamc'
19
+ require 'eventmachine'
20
+
21
+ class Test::Unit::TestCase
22
+ def example_message(name)
23
+ message_path = File.expand_path("examples/messages/#{name}.txt", File.dirname(__FILE__))
24
+
25
+ File.open(message_path).read
26
+ end
27
+
28
+ def example_report(name)
29
+ message_path = File.expand_path("examples/reports/#{name}.txt", File.dirname(__FILE__))
30
+
31
+ File.open(message_path).read
32
+ end
33
+
34
+ def connection_options
35
+ config_file = File.expand_path('config/spamassassin.yml', File.dirname(__FILE__))
36
+
37
+ Hash[
38
+ YAML.load(File.open(config_file)).collect do |key, value|
39
+ [ key.to_sym, value ]
40
+ end
41
+ ]
42
+ end
43
+
44
+ def eventmachine
45
+ EventMachine.run do
46
+ Fiber.new do
47
+ yield
48
+ EventMachine.stop_event_loop
49
+ end.resume
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ require_relative '../helper'
2
+
3
+ class TestEmSpamc < Test::Unit::TestCase
4
+ end
@@ -0,0 +1,44 @@
1
+ require_relative '../helper'
2
+
3
+ class TestEmSpamcConnection < Test::Unit::TestCase
4
+ def test_ping
5
+ eventmachine do
6
+ result = EmSpamc::Connection.ping(connection_options)
7
+
8
+ assert result
9
+ end
10
+ end
11
+
12
+ def test_symbols
13
+ eventmachine do
14
+ message = example_message(:sample)
15
+ result = EmSpamc::Connection.symbols(message, connection_options)
16
+
17
+ assert result
18
+ end
19
+ end
20
+
21
+ def test_report
22
+ eventmachine do
23
+ message = example_message(:sample)
24
+
25
+ result = EmSpamc::Connection.report(message, connection_options)
26
+
27
+ assert result
28
+ assert result.report
29
+ assert result.headers[:score]
30
+
31
+ assert_equal result.spam?, false
32
+ end
33
+ end
34
+
35
+ def test_check
36
+ eventmachine do
37
+ message = example_message(:sample)
38
+
39
+ result = EmSpamc::Connection.check(message)
40
+
41
+ assert result
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '../helper'
2
+
3
+ class TestEmSpamcHeaderParser < Test::Unit::TestCase
4
+ def test_score_header
5
+ report = example_report('spam-report')
6
+ headers = EmSpamc::HeaderParser.parse(report)
7
+ assert_equal headers[:spam], true
8
+ assert headers[:score] >= 0
9
+ end
10
+
11
+ def test_zero_header
12
+ report = example_report('zero-report')
13
+ headers = EmSpamc::HeaderParser.parse(report)
14
+ assert headers[:score]
15
+ assert_equal headers[:score], 0
16
+ assert_equal headers[:spam], false
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../helper'
2
+
3
+ class TestEmSpamcReportParser < Test::Unit::TestCase
4
+ def test_parsing_spam
5
+ eventmachine do
6
+ report = example_report('spam-report')
7
+ rules = EmSpamc::ReportParser.parse(report)
8
+ assert rules
9
+ end
10
+ end
11
+
12
+ def test_parsing_zero
13
+ eventmachine do
14
+ report = example_report('zero-report')
15
+ rules = EmSpamc::ReportParser.parse(report)
16
+ assert rules
17
+ assert_equal rules, []
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../helper'
2
+
3
+ class TestEmSpamcConnection < Test::Unit::TestCase
4
+ def test_defaults
5
+ result = EmSpamc::Result.new
6
+
7
+ assert_equal nil, result.headers
8
+ assert_equal nil, result.report
9
+ end
10
+
11
+ def test_assignment
12
+ result = EmSpamc::Result.new
13
+
14
+ assert_equal nil, result.headers
15
+
16
+ result.headers = { :code => 0 }
17
+
18
+ assert_equal 0, result.headers[:code]
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-spamc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Scott Tadman
9
+ - Stephan Leroux
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-04-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: bundler
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: jeweler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.8.4
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: 1.8.4
63
+ description: Implementation of the "spamc" protocol for EventMachine
64
+ email: github@tadman.ca
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files:
68
+ - LICENSE.txt
69
+ - README.md
70
+ files:
71
+ - .document
72
+ - Gemfile
73
+ - LICENSE.txt
74
+ - README.md
75
+ - Rakefile
76
+ - VERSION
77
+ - em-spamc.gemspec
78
+ - lib/em-spamc.rb
79
+ - lib/em_spamc.rb
80
+ - lib/em_spamc/connection.rb
81
+ - lib/em_spamc/header_parser.rb
82
+ - lib/em_spamc/report_parser.rb
83
+ - lib/em_spamc/result.rb
84
+ - test/config/spamassassin.yml
85
+ - test/config/spamassassin.yml.example
86
+ - test/examples/messages/sample.txt
87
+ - test/examples/messages/zero-score.txt
88
+ - test/examples/reports/spam-report.txt
89
+ - test/examples/reports/zero-report.txt
90
+ - test/examples/sample.txt
91
+ - test/helper.rb
92
+ - test/unit/test_em_spamc.rb
93
+ - test/unit/test_em_spamc_connection.rb
94
+ - test/unit/test_em_spamc_header_parser.rb
95
+ - test/unit/test_em_spamc_report_parser.rb
96
+ - test/unit/test_em_spamc_result.rb
97
+ homepage: http://github.com/twg/em-spamc
98
+ licenses:
99
+ - MIT
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 1.8.25
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: SpamAssassin Connection for EventMachine
122
+ test_files: []