astrotrain 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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