astrotrain 0.3.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.
Files changed (57) hide show
  1. data/.gitignore +26 -0
  2. data/LICENSE +20 -0
  3. data/README +47 -0
  4. data/Rakefile +122 -0
  5. data/VERSION +1 -0
  6. data/astrotrain.gemspec +129 -0
  7. data/config/sample.rb +12 -0
  8. data/lib/astrotrain.rb +53 -0
  9. data/lib/astrotrain/api.rb +52 -0
  10. data/lib/astrotrain/logged_mail.rb +46 -0
  11. data/lib/astrotrain/mapping.rb +157 -0
  12. data/lib/astrotrain/mapping/http_post.rb +18 -0
  13. data/lib/astrotrain/mapping/jabber.rb +28 -0
  14. data/lib/astrotrain/mapping/transport.rb +55 -0
  15. data/lib/astrotrain/message.rb +330 -0
  16. data/lib/astrotrain/tmail.rb +58 -0
  17. data/lib/astrotrain/worker.rb +65 -0
  18. data/lib/vendor/rest-client/README.rdoc +104 -0
  19. data/lib/vendor/rest-client/Rakefile +84 -0
  20. data/lib/vendor/rest-client/bin/restclient +65 -0
  21. data/lib/vendor/rest-client/foo.diff +66 -0
  22. data/lib/vendor/rest-client/lib/rest_client.rb +188 -0
  23. data/lib/vendor/rest-client/lib/rest_client/net_http_ext.rb +23 -0
  24. data/lib/vendor/rest-client/lib/rest_client/payload.rb +185 -0
  25. data/lib/vendor/rest-client/lib/rest_client/request_errors.rb +75 -0
  26. data/lib/vendor/rest-client/lib/rest_client/resource.rb +103 -0
  27. data/lib/vendor/rest-client/rest-client.gemspec +18 -0
  28. data/lib/vendor/rest-client/spec/base.rb +5 -0
  29. data/lib/vendor/rest-client/spec/master_shake.jpg +0 -0
  30. data/lib/vendor/rest-client/spec/payload_spec.rb +71 -0
  31. data/lib/vendor/rest-client/spec/request_errors_spec.rb +44 -0
  32. data/lib/vendor/rest-client/spec/resource_spec.rb +52 -0
  33. data/lib/vendor/rest-client/spec/rest_client_spec.rb +219 -0
  34. data/test/api_test.rb +28 -0
  35. data/test/fixtures/apple_multipart.txt +100 -0
  36. data/test/fixtures/bad_content_type.txt +27 -0
  37. data/test/fixtures/basic.txt +14 -0
  38. data/test/fixtures/custom.txt +15 -0
  39. data/test/fixtures/fwd.txt +0 -0
  40. data/test/fixtures/gb2312_encoding.txt +16 -0
  41. data/test/fixtures/gb2312_encoding_invalid.txt +15 -0
  42. data/test/fixtures/html.txt +16 -0
  43. data/test/fixtures/iso-8859-1.txt +13 -0
  44. data/test/fixtures/mapped.txt +13 -0
  45. data/test/fixtures/multipart.txt +213 -0
  46. data/test/fixtures/multipart2.txt +213 -0
  47. data/test/fixtures/multiple.txt +13 -0
  48. data/test/fixtures/multiple_delivered_to.txt +14 -0
  49. data/test/fixtures/multiple_with_body_recipients.txt +15 -0
  50. data/test/fixtures/reply.txt +16 -0
  51. data/test/fixtures/utf-8.txt +13 -0
  52. data/test/logged_mail_test.rb +67 -0
  53. data/test/mapping_test.rb +129 -0
  54. data/test/message_test.rb +440 -0
  55. data/test/test_helper.rb +57 -0
  56. data/test/transport_test.rb +111 -0
  57. metadata +225 -0
@@ -0,0 +1,188 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ require File.dirname(__FILE__) + '/rest_client/resource'
5
+ require File.dirname(__FILE__) + '/rest_client/request_errors'
6
+ require File.dirname(__FILE__) + '/rest_client/payload'
7
+ require File.dirname(__FILE__) + '/rest_client/net_http_ext'
8
+
9
+ # This module's static methods are the entry point for using the REST client.
10
+ #
11
+ # # GET
12
+ # xml = RestClient.get 'http://example.com/resource'
13
+ # jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
14
+ #
15
+ # # authentication and SSL
16
+ # RestClient.get 'https://user:password@example.com/private/resource'
17
+ #
18
+ # # POST or PUT with a hash sends parameters as a urlencoded form body
19
+ # RestClient.post 'http://example.com/resource', :param1 => 'one'
20
+ #
21
+ # # nest hash parameters
22
+ # RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
23
+ #
24
+ # # POST and PUT with raw payloads
25
+ # RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
26
+ # RestClient.post 'http://example.com/resource.xml', xml_doc
27
+ # RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
28
+ #
29
+ # # DELETE
30
+ # RestClient.delete 'http://example.com/resource'
31
+ #
32
+ # For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
33
+ #
34
+ # >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
35
+ # => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
36
+ #
37
+ module RestClient
38
+ def self.get(url, headers={}, &b)
39
+ Request.execute(:method => :get,
40
+ :url => url,
41
+ :headers => headers, &b)
42
+ end
43
+
44
+ def self.post(url, payload, headers={}, &b)
45
+ Request.execute(:method => :post,
46
+ :url => url,
47
+ :payload => payload,
48
+ :headers => headers, &b)
49
+ end
50
+
51
+ def self.put(url, payload, headers={}, &b)
52
+ Request.execute(:method => :put,
53
+ :url => url,
54
+ :payload => payload,
55
+ :headers => headers, &b)
56
+ end
57
+
58
+ def self.delete(url, headers={}, &b)
59
+ Request.execute(:method => :delete,
60
+ :url => url,
61
+ :headers => headers, &b)
62
+ end
63
+
64
+ # Internal class used to build and execute the request.
65
+ class Request
66
+ attr_reader :method, :url, :headers, :user, :password
67
+
68
+ def self.execute(args, &b)
69
+ new(args).execute(&b)
70
+ end
71
+
72
+ def initialize(args)
73
+ @method = args[:method] or raise ArgumentError, "must pass :method"
74
+ @url = args[:url] or raise ArgumentError, "must pass :url"
75
+ @payload = Payload.generate(args[:payload] || '')
76
+ @headers = args[:headers] || {}
77
+ @user = args[:user]
78
+ @password = args[:password]
79
+ end
80
+
81
+ def execute(&b)
82
+ execute_inner(&b)
83
+ rescue Redirect => e
84
+ @url = e.url
85
+ execute(&b)
86
+ end
87
+
88
+ def execute_inner(&b)
89
+ uri = parse_url_with_auth(url)
90
+ transmit(uri, net_http_class(method).new(uri.request_uri, make_headers(headers)), payload, &b)
91
+ end
92
+
93
+ def make_headers(user_headers)
94
+ final = {}
95
+ merged = default_headers.merge(user_headers)
96
+ merged.keys.each do |key|
97
+ final[key.to_s.gsub(/_/, '-').capitalize] = merged[key]
98
+ end
99
+ final
100
+ end
101
+
102
+ def net_http_class(method)
103
+ Net::HTTP.const_get(method.to_s.capitalize)
104
+ end
105
+
106
+ def parse_url(url)
107
+ url = "http://#{url}" unless url.match(/^http/)
108
+ URI.parse(url)
109
+ end
110
+
111
+ def parse_url_with_auth(url)
112
+ uri = parse_url(url)
113
+ @user = uri.user if uri.user
114
+ @password = uri.password if uri.password
115
+ uri
116
+ end
117
+
118
+ def process_payload(p=nil, parent_key=nil)
119
+ unless p.is_a?(Hash)
120
+ p
121
+ else
122
+ @headers[:content_type] ||= 'application/x-www-form-urlencoded'
123
+ p.keys.map do |k|
124
+ key = parent_key ? "#{parent_key}[#{k}]" : k
125
+ if p[k].is_a? Hash
126
+ process_payload(p[k], key)
127
+ else
128
+ value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
129
+ "#{key}=#{value}"
130
+ end
131
+ end.join("&")
132
+ end
133
+ end
134
+
135
+ def transmit(uri, req, payload, &b)
136
+ setup_credentials(req)
137
+
138
+ net = Net::HTTP.new(uri.host, uri.port)
139
+ net.use_ssl = uri.is_a?(URI::HTTPS)
140
+ net.start do |http|
141
+ ## Ok. I know this is weird but it's a hack for now
142
+ ## this lets process_result determine if it should read the body
143
+ ## into memory or not
144
+ process_result(http.request(req, payload || "", &b), &b)
145
+ end
146
+ rescue EOFError
147
+ raise RestClient::ServerBrokeConnection
148
+ rescue Timeout::Error
149
+ raise RestClient::RequestTimeout
150
+ ensure
151
+ payload.close
152
+ end
153
+
154
+ def setup_credentials(req)
155
+ req.basic_auth(user, password) if user
156
+ end
157
+
158
+ def process_result(res, &b)
159
+ if %w(200 201 202).include? res.code
160
+ return res.body unless b
161
+ elsif %w(301 302 303).include? res.code
162
+ url = res.header['Location']
163
+
164
+ if url !~ /^http/
165
+ uri = URI.parse(@url)
166
+ uri.path = "/#{url}".squeeze('/')
167
+ url = uri.to_s
168
+ end
169
+
170
+ raise Redirect, url
171
+ elsif res.code == "401"
172
+ raise Unauthorized
173
+ elsif res.code == "404"
174
+ raise ResourceNotFound
175
+ else
176
+ raise RequestFailed, res
177
+ end
178
+ end
179
+
180
+ def payload
181
+ @payload
182
+ end
183
+
184
+ def default_headers
185
+ @payload.headers.merge({ :accept => 'application/xml' })
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Replace the request method in Net::HTTP to sniff the body type
3
+ # and set the stream if appropriate
4
+ #
5
+ # Taken from:
6
+ # http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
7
+
8
+ Net::HTTP.ssl_context_accessor(:tmp_dh_callback)
9
+
10
+ module Net
11
+ class HTTP
12
+ alias __request__ request
13
+
14
+ def request(req, body=nil, &block)
15
+ if body != nil && body.respond_to?(:read)
16
+ req.body_stream = body
17
+ return __request__(req, nil, &block)
18
+ else
19
+ return __request__(req, body, &block)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,185 @@
1
+ require "tempfile"
2
+ require "stringio"
3
+
4
+ module RestClient
5
+ module Payload
6
+ extend self
7
+
8
+ def generate(params)
9
+ if params.is_a?(String)
10
+ Base.new(params)
11
+ elsif params.delete(:multipart) == true ||
12
+ params.any? { |_,v| v.respond_to?(:path) && v.respond_to?(:read) }
13
+ Multipart.new(params)
14
+ else
15
+ UrlEncoded.new(params)
16
+ end
17
+ end
18
+
19
+ class Base
20
+ def initialize(params)
21
+ build_stream(params)
22
+ end
23
+
24
+ def build_stream(params)
25
+ @stream = StringIO.new(params)
26
+ @stream.seek(0)
27
+ end
28
+
29
+ def read(bytes=nil)
30
+ @stream.read(bytes)
31
+ end
32
+ alias :to_s :read
33
+
34
+ def escape(v)
35
+ URI.escape(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
36
+ end
37
+
38
+ def headers
39
+ { 'Content-Length' => size.to_s }
40
+ end
41
+
42
+ def size
43
+ @stream.size
44
+ end
45
+ alias :length :size
46
+
47
+ def close
48
+ @stream.close
49
+ end
50
+ end
51
+
52
+ class UrlEncoded < Base
53
+ def build_stream(params)
54
+ @stream = StringIO.new(params.map do |k,v|
55
+ "#{escape(k)}=#{escape(v)}"
56
+ end.join("&"))
57
+ @stream.seek(0)
58
+ end
59
+
60
+ def headers
61
+ super.merge({ 'Content-Type' => 'application/x-www-form-urlencoded' })
62
+ end
63
+ end
64
+
65
+ class Multipart < Base
66
+ EOL = "\r\n"
67
+
68
+ def build_stream(params)
69
+ b = "--#{boundary}"
70
+
71
+ @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
72
+ @stream.write(b)
73
+ params.each do |k,v|
74
+ @stream.write(EOL)
75
+ if v.respond_to?(:read) && v.respond_to?(:path)
76
+ create_file_field(@stream, k,v)
77
+ else
78
+ create_regular_field(@stream, k,v)
79
+ end
80
+ @stream.write(EOL + b)
81
+ end
82
+ @stream.write('--')
83
+ @stream.write(EOL)
84
+ @stream.seek(0)
85
+ end
86
+
87
+ def create_regular_field(s, k, v)
88
+ s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"")
89
+ s.write(EOL)
90
+ s.write(EOL)
91
+ s.write(v)
92
+ end
93
+
94
+ def create_file_field(s, k, v)
95
+ begin
96
+ s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{v.path}\"#{EOL}")
97
+ s.write("Content-Type: #{mime_for(v.path)}#{EOL}")
98
+ s.write(EOL)
99
+ while data = v.read(8124)
100
+ s.write(data)
101
+ end
102
+ ensure
103
+ v.close
104
+ end
105
+ end
106
+
107
+ def mime_for(path)
108
+ ext = File.extname(path)[1..-1]
109
+ MIME_TYPES[ext] || 'text/plain'
110
+ end
111
+
112
+ def boundary
113
+ @boundary ||= rand(1_000_000).to_s
114
+ end
115
+
116
+ def headers
117
+ super.merge({'Content-Type' => %Q{multipart/form-data; boundary="#{boundary}"}})
118
+ end
119
+
120
+ def close
121
+ @stream.close
122
+ end
123
+ end
124
+
125
+ # :stopdoc:
126
+ # From WEBrick.
127
+ MIME_TYPES = {
128
+ "ai" => "application/postscript",
129
+ "asc" => "text/plain",
130
+ "avi" => "video/x-msvideo",
131
+ "bin" => "application/octet-stream",
132
+ "bmp" => "image/bmp",
133
+ "class" => "application/octet-stream",
134
+ "cer" => "application/pkix-cert",
135
+ "crl" => "application/pkix-crl",
136
+ "crt" => "application/x-x509-ca-cert",
137
+ "css" => "text/css",
138
+ "dms" => "application/octet-stream",
139
+ "doc" => "application/msword",
140
+ "dvi" => "application/x-dvi",
141
+ "eps" => "application/postscript",
142
+ "etx" => "text/x-setext",
143
+ "exe" => "application/octet-stream",
144
+ "gif" => "image/gif",
145
+ "gz" => "application/x-gzip",
146
+ "htm" => "text/html",
147
+ "html" => "text/html",
148
+ "jpe" => "image/jpeg",
149
+ "jpeg" => "image/jpeg",
150
+ "jpg" => "image/jpeg",
151
+ "js" => "text/javascript",
152
+ "lha" => "application/octet-stream",
153
+ "lzh" => "application/octet-stream",
154
+ "mov" => "video/quicktime",
155
+ "mpe" => "video/mpeg",
156
+ "mpeg" => "video/mpeg",
157
+ "mpg" => "video/mpeg",
158
+ "pbm" => "image/x-portable-bitmap",
159
+ "pdf" => "application/pdf",
160
+ "pgm" => "image/x-portable-graymap",
161
+ "png" => "image/png",
162
+ "pnm" => "image/x-portable-anymap",
163
+ "ppm" => "image/x-portable-pixmap",
164
+ "ppt" => "application/vnd.ms-powerpoint",
165
+ "ps" => "application/postscript",
166
+ "qt" => "video/quicktime",
167
+ "ras" => "image/x-cmu-raster",
168
+ "rb" => "text/plain",
169
+ "rd" => "text/plain",
170
+ "rtf" => "application/rtf",
171
+ "sgm" => "text/sgml",
172
+ "sgml" => "text/sgml",
173
+ "tif" => "image/tiff",
174
+ "tiff" => "image/tiff",
175
+ "txt" => "text/plain",
176
+ "xbm" => "image/x-xbitmap",
177
+ "xls" => "application/vnd.ms-excel",
178
+ "xml" => "text/xml",
179
+ "xpm" => "image/x-xpixmap",
180
+ "xwd" => "image/x-xwindowdump",
181
+ "zip" => "application/zip",
182
+ }
183
+ # :startdoc:
184
+ end
185
+ end
@@ -0,0 +1,75 @@
1
+ module RestClient
2
+ # This is the base RestClient exception class. Rescue it if you want to
3
+ # catch any exception that your request might raise
4
+ class Exception < RuntimeError
5
+ def message(default=nil)
6
+ self.class::ErrorMessage
7
+ end
8
+ end
9
+
10
+ # A redirect was encountered; caught by execute to retry with the new url.
11
+ class Redirect < Exception
12
+ ErrorMessage = "Redirect"
13
+
14
+ attr_accessor :url
15
+ def initialize(url)
16
+ @url = url
17
+ end
18
+ end
19
+
20
+ # Authorization is required to access the resource specified.
21
+ class Unauthorized < Exception
22
+ ErrorMessage = 'Unauthorized'
23
+ end
24
+
25
+ # No resource was found at the given URL.
26
+ class ResourceNotFound < Exception
27
+ ErrorMessage = 'Resource not found'
28
+ end
29
+
30
+ # The server broke the connection prior to the request completing.
31
+ class ServerBrokeConnection < Exception
32
+ ErrorMessage = 'Server broke connection'
33
+ end
34
+
35
+ # The server took too long to respond.
36
+ class RequestTimeout < Exception
37
+ ErrorMessage = 'Request timed out'
38
+ end
39
+
40
+ # The request failed, meaning the remote HTTP server returned a code other
41
+ # than success, unauthorized, or redirect.
42
+ #
43
+ # The exception message attempts to extract the error from the XML, using
44
+ # format returned by Rails: <errors><error>some message</error></errors>
45
+ #
46
+ # You can get the status code by e.http_code, or see anything about the
47
+ # response via e.response. For example, the entire result body (which is
48
+ # probably an HTML error page) is e.response.body.
49
+ class RequestFailed < Exception
50
+ attr_accessor :response
51
+
52
+ def initialize(response=nil)
53
+ @response = response
54
+ end
55
+
56
+ def http_code
57
+ @response.code.to_i if @response
58
+ end
59
+
60
+ def message
61
+ "HTTP status code #{http_code}"
62
+ end
63
+
64
+ def to_s
65
+ message
66
+ end
67
+ end
68
+ end
69
+
70
+ # backwards compatibility
71
+ class RestClient::Request
72
+ Redirect = RestClient::Redirect
73
+ Unauthorized = RestClient::Unauthorized
74
+ RequestFailed = RestClient::RequestFailed
75
+ end