sonar_rexchange 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ require 'rexchange/exchange_request'
2
+
3
+ module RExchange
4
+ # Used for arbitrary SEARCH requests
5
+ class DavGetRequest < ExchangeRequest
6
+ METHOD = 'GET'
7
+ REQUEST_HAS_BODY = false
8
+ RESPONSE_HAS_BODY = true
9
+
10
+ def self.execute(credentials, url, &b)
11
+ begin
12
+ options = {
13
+ :path => url,
14
+ :headers => {
15
+ 'Translate' => 'f' # Microsoft IIS 5.0 "Translate: f" Source Disclosure Vulnerability (http://www.securityfocus.com/bid/1578)
16
+ }
17
+ }
18
+ response = super credentials, options
19
+ yield response if b
20
+ response
21
+ rescue RException => e
22
+ raise e
23
+ rescue Exception => e
24
+ raise RException.new(options[:request], response, e)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require 'rexchange/exchange_request'
2
+
3
+ module RExchange
4
+ # Used to make a new collection (aka folder).
5
+ class DavMkcolRequest < ExchangeRequest
6
+ METHOD = 'MKCOL'
7
+ REQUEST_HAS_BODY = false
8
+ RESPONSE_HAS_BODY = false
9
+
10
+ def self.execute(credentials, folder_url, &b)
11
+ begin
12
+ options = {
13
+ :path => folder_url
14
+ }
15
+
16
+ response = super credentials, options
17
+ yield response if b
18
+ response
19
+ rescue RException => e
20
+ raise e
21
+ rescue Exception => e
22
+ raise RException.new(options[:request], response, e)
23
+ end
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'rexchange/exchange_request'
2
+
3
+ module RExchange
4
+ # Used to move entities to different locations in the accessable mailbox.
5
+ class DavMoveRequest < ExchangeRequest
6
+ METHOD = 'MOVE'
7
+ REQUEST_HAS_BODY = false
8
+ RESPONSE_HAS_BODY = false
9
+
10
+ def self.execute(credentials, source, destination, &b)
11
+ begin
12
+ options = {
13
+ :headers => {
14
+ 'Destination' => destination
15
+ },
16
+ :path => source
17
+ }
18
+
19
+ response = super credentials, options
20
+ yield response if b
21
+ response
22
+ rescue RException => e
23
+ raise e
24
+ rescue Exception => e
25
+ raise RException.new(options[:request], response, e)
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,49 @@
1
+ require 'rexchange/exchange_request'
2
+
3
+ module RExchange
4
+ # Used to move entities to different locations in the accessable mailbox.
5
+ class DavProppatchRequest < ExchangeRequest
6
+ METHOD = 'PROPPATCH'
7
+
8
+
9
+ def self.request_body(property, name_space, value)
10
+ <<-eos
11
+ <D:propertyupdate xmlns:D = "DAV:">
12
+ <D:set>
13
+ <D:prop>
14
+ <X:#{property} xmlns:X = "#{name_space}:">#{value}</X:read>
15
+ </D:prop>
16
+ </D:set>
17
+ </D:propertyupdate>
18
+ eos
19
+ end
20
+
21
+ def self.execute(credentials, uri, property, name_space, value, &b)
22
+ begin
23
+ options = {}
24
+ options[:path] = uri
25
+ headers = options[:headers] ||= {}
26
+ headers['Cookie'] = credentials.auth_cookie if credentials.auth_cookie
27
+ options[:body] = request_body(property, name_space, value)
28
+
29
+ response = self.exchange_request(credentials, options)
30
+ case response
31
+ when Net::HTTPClientError then
32
+ self.authenticate(credentials)
33
+ #repeat exchange_request after authentication with the auth-cookies
34
+ headers = options[:headers] ||= {}
35
+ headers['Cookie'] = credentials.auth_cookie if credentials.auth_cookie
36
+ response = self.exchange_request(credentials, options)
37
+ end
38
+ yield response if b
39
+ response
40
+ rescue RException => e
41
+ raise e
42
+ rescue Exception => e
43
+ raise RException.new(options[:request], response, e)
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,8 @@
1
+ require 'rexchange/exchange_request'
2
+
3
+ module RExchange
4
+ # Used for arbitrary SEARCH requests
5
+ class DavSearchRequest < ExchangeRequest
6
+ METHOD = 'SEARCH'
7
+ end
8
+ end
@@ -0,0 +1,86 @@
1
+ require 'net/https'
2
+
3
+ module RExchange
4
+
5
+ # Exchange Server's WebDAV interface is non-standard, so
6
+ # we create this simple wrapper to extend the 'net/http'
7
+ # library and add the request methods we need.
8
+ class ExchangeRequest < Net::HTTPRequest
9
+ REQUEST_HAS_BODY = true
10
+ RESPONSE_HAS_BODY = true
11
+
12
+ def self.authenticate(credentials)
13
+ owa_uri = credentials.owa_uri
14
+ if owa_uri
15
+ http = Net::HTTP.new(owa_uri.host, owa_uri.port)
16
+ http.set_debug_output(RExchange::DEBUG_STREAM) if RExchange::DEBUG_STREAM
17
+ req = Net::HTTP::Post.new(owa_uri.path)
18
+ destination = owa_uri.scheme+"://"+owa_uri.host+(owa_uri.port ? ':'+owa_uri.port.to_s : '')
19
+ req.body = "destination=#{destination}&username=#{credentials.user}&password=#{credentials.password}"
20
+ http.use_ssl = owa_uri.scheme ? (owa_uri.scheme.downcase == 'https') : false
21
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
22
+ res = http.request(req)
23
+ credentials.auth_cookie = res.header["set-cookie"].split(',').map(&:strip).map{|c| c.split(';')[0]}.reverse.join('; ') if res.header["set-cookie"]
24
+ end
25
+ end
26
+
27
+ def self.exchange_request(credentials, options = {})
28
+ http = Net::HTTP.new(credentials.dav_uri.host, credentials.dav_uri.port)
29
+ http.set_debug_output(RExchange::DEBUG_STREAM) if RExchange::DEBUG_STREAM
30
+ request_path = options[:path] || credentials.dav_uri.path
31
+ req = self.new(request_path)
32
+ options[:request] = req
33
+ req.basic_auth credentials.user, credentials.password #if !credentials.is_owa?
34
+ req.content_type = 'text/xml'
35
+ req.add_field 'host', credentials.dav_uri.host
36
+
37
+ if options[:headers]
38
+ options[:headers].each_pair do |k, v|
39
+ req.add_field k, v
40
+ end
41
+ end
42
+
43
+ req.body = options[:body] if REQUEST_HAS_BODY
44
+ http.use_ssl = credentials.dav_use_ssl?
45
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
46
+ response = http.request(req) if RESPONSE_HAS_BODY
47
+
48
+ end
49
+
50
+ def self.execute(credentials, options = {}, &b)
51
+ begin
52
+ headers = options[:headers] ||= {}
53
+ headers['Cookie'] = credentials.auth_cookie if credentials.auth_cookie
54
+ response = self.exchange_request(credentials, options)
55
+ case response
56
+ when Net::HTTPClientError then
57
+ self.authenticate(credentials)
58
+ #repeat exchange_request after authentication with the auth-cookies
59
+ headers = options[:headers] ||= {}
60
+ headers['Cookie'] = credentials.auth_cookie if credentials.auth_cookie
61
+ response = self.exchange_request(credentials, options)
62
+ end
63
+
64
+ raise 'NOT 2xx NOR 3xx: ' + response.inspect.to_s unless (Net::HTTPSuccess === response || Net::HTTPRedirection === response)
65
+
66
+ yield response if b
67
+ response
68
+ rescue RException => e
69
+ raise e
70
+ rescue Exception => e
71
+ #puts e.backtrace.map{|e| e.to_s}.join("\n")
72
+ raise RException.new(options[:request], response, e)
73
+ end
74
+
75
+ end
76
+
77
+ private
78
+ # You can not instantiate an ExchangeRequest externally.
79
+ def initialize(*args)
80
+ super
81
+ end
82
+ end
83
+ end
84
+
85
+
86
+
@@ -0,0 +1,108 @@
1
+ require 'rexml/document'
2
+ require 'net/https'
3
+ require 'rexchange/dav_search_request'
4
+ require 'rexchange/dav_mkcol_request'
5
+ require 'rexchange/message'
6
+ require 'rexchange/contact'
7
+ require 'rexchange/appointment'
8
+ require 'rexchange/note'
9
+ require 'rexchange/task'
10
+ require 'rexchange/message_href'
11
+ require 'uri'
12
+
13
+ module RExchange
14
+
15
+ class FolderNotFoundError < StandardError
16
+ end
17
+
18
+ class Folder
19
+ include REXML
20
+
21
+ attr_reader :credentials, :displayname
22
+
23
+ def initialize(credentials, parent, displayname, href, content_type)
24
+ @credentials, @parent, @href = credentials, parent, href
25
+ @content_type = CONTENT_TYPES[content_type] || Message
26
+ @displayname = displayname
27
+ end
28
+
29
+ # Used to access subfolders.
30
+ def method_missing(sym, *args)
31
+
32
+ if folders_hash.has_key?(sym.to_s)
33
+ folders_hash[sym.to_s]
34
+ else
35
+ puts folders_hash.keys.inspect
36
+ puts sym.to_s
37
+ raise FolderNotFoundError.new("#{sym} is not a subfolder of #{@displayname} - #{@href}")
38
+ end
39
+ end
40
+
41
+ include Enumerable
42
+
43
+ def message_hrefs(href_regex)
44
+ RExchange::MessageHref::find_message_hrefs(href_regex, @credentials, to_s)
45
+ end
46
+
47
+ # Iterate through each entry in this folder
48
+ def each
49
+ @content_type::find(@credentials, to_s).each do |item|
50
+ yield item
51
+ end
52
+ end
53
+
54
+ # Not Implemented!
55
+ def search(conditions = {})
56
+ raise NotImplementedError.new('Bad Touch!')
57
+ @content_type::find(@credentials, to_s, conditions)
58
+ end
59
+
60
+ # Return an Array of subfolders for this folder
61
+ def folders
62
+ @folders ||=
63
+ begin
64
+ request_body = <<-eos
65
+ <D:searchrequest xmlns:D = "DAV:">
66
+ <D:sql>
67
+ SELECT "DAV:displayname", "DAV:contentclass"
68
+ FROM SCOPE('shallow traversal of "#{@href}"')
69
+ WHERE "DAV:ishidden" = false
70
+ AND "DAV:isfolder" = true
71
+ </D:sql>
72
+ </D:searchrequest>
73
+ eos
74
+ folders = []
75
+ DavSearchRequest.execute(@credentials, :body => request_body) do |response|
76
+
77
+
78
+ # iterate through folders query and add a new Folder
79
+ # object for each, under a normalized name.
80
+ xpath_query = "/*/a:response[a:propstat/a:status/text() = 'HTTP/1.1 200 OK']"
81
+ Document.new(response.body).elements.each(xpath_query) do |m|
82
+ href = m.elements['a:href'].text
83
+ displayname = m.elements['a:propstat/a:prop/a:displayname'].text
84
+ contentclass = m.elements['a:propstat/a:prop/a:contentclass'].text
85
+ folders << Folder.new(@credentials, self, displayname, href, contentclass.split(':').last.sub(/folder$/, ''))
86
+ end
87
+
88
+ end
89
+ folders
90
+ end
91
+
92
+ end
93
+
94
+ def folders_hash
95
+ @folders_hash ||= folders.inject({}){|memo, f| memo[f.displayname.normalize]=f; memo}
96
+ end
97
+
98
+ def make_subfolder(subfolder)
99
+ path = @href.ensure_ends_with("/") + subfolder.ensure_ends_with("/")
100
+ DavMkcolRequest.execute(@credentials, path)
101
+ end
102
+
103
+ # Return the absolute path to this folder (but not the full URI)
104
+ def to_s
105
+ @href
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,124 @@
1
+ require 'rexml/document'
2
+ require 'ostruct'
3
+ require 'rexchange/dav_move_request'
4
+ require 'rexchange/dav_get_request'
5
+ require 'time'
6
+
7
+ module RExchange
8
+
9
+ class Folder
10
+ CONTENT_TYPES = {}
11
+ end
12
+
13
+ class GenericItem
14
+ include REXML
15
+ include Enumerable
16
+
17
+ attr_accessor :attributes
18
+
19
+ def initialize(session, dav_property_node)
20
+ @attributes = {}
21
+ @session = session
22
+
23
+ dav_property_node.elements.each do |element|
24
+ namespaced_name = element.namespace + element.name
25
+
26
+ if element.name =~ /date$/i || self.class::ATTRIBUTE_MAPPINGS.find { |k, v| v == namespaced_name && k.to_s =~ /\_(at|on)$/ }
27
+ @attributes[namespaced_name] = Time::parse(element.text) rescue element.text
28
+ else
29
+ @attributes[namespaced_name] = element.text
30
+ end
31
+ end
32
+ end
33
+
34
+ # Set the default CONTENT_CLASS to the class name, and define a
35
+ # dynamic query method for the derived class.
36
+ def self.inherited(base)
37
+ base.const_set('CONTENT_CLASS', base.to_s.split('::').last.downcase)
38
+
39
+ def base.query(path)
40
+ <<-QBODY
41
+ SELECT
42
+ #{self::ATTRIBUTE_MAPPINGS.values.map { |f| '"' + f + '"' }.join(',')}
43
+ FROM SCOPE('shallow traversal of "#{path}"')
44
+ WHERE "DAV:ishidden" = false
45
+ AND "DAV:isfolder" = false
46
+ AND "DAV:contentclass" = 'urn:content-classes:#{self::CONTENT_CLASS}'
47
+ QBODY
48
+ end
49
+ end
50
+
51
+ # This handy method is meant to be called from any inheriting
52
+ # classes. It is used to bind types of folders to particular
53
+ # Entity classes so that the folder knows what type it's
54
+ # enumerating. So for a "calendarfolder" you'd call:
55
+ # set_folder_type 'calendarfolder' # or just 'calendar'
56
+ def self.set_folder_type(dav_name)
57
+ Folder::CONTENT_TYPES[dav_name.sub(/folder$/, '')] = self
58
+ end
59
+
60
+ # --Normally Not Used--
61
+ # By default the CONTENT_CLASS is determined by the name
62
+ # of your class. So for the Appointment class the
63
+ # CONTENT_CLASS would be 'appointment'.
64
+ # If for some reason this convention doesn't suit you,
65
+ # you can use this method to set the appropriate value
66
+ # (which is used in queries).
67
+ # For example, the DAV:content-class for contacts is:
68
+ # 'urn:content-classes:person'
69
+ # Person doesn't strike me as the best name for our class though.
70
+ # Most people would refer to an entry in a Contacts folder as
71
+ # a Contact. So that's what we call our class, and we use this method
72
+ # to make sure everything still works as it should.
73
+ def self.set_content_class(dav_name)
74
+ verbosity, $VERBOSE = $VERBOSE, nil # disable warnings for the next operation
75
+ const_set('CONTENT_CLASS', dav_name)
76
+ $VERBOSE = verbosity # revert to the original verbosity
77
+ end
78
+
79
+ # Defines what attributes are used in queries, and
80
+ # what methods they map to in instances. You should
81
+ # pass a Hash of method_name symbols and namespaced-attribute-name pairs.
82
+ def self.attribute_mappings(mappings)
83
+
84
+ mappings.merge! :uid => 'DAV:uid',
85
+ :modified_at => 'DAV:getlastmodified',
86
+ :href => 'DAV:href'
87
+
88
+ const_set('ATTRIBUTE_MAPPINGS', mappings)
89
+
90
+ mappings.each_pair do |k, v|
91
+
92
+ define_method(k) do
93
+ @attributes[v]
94
+ end
95
+
96
+ define_method("#{k.to_s.sub(/\?$/, '')}=") do |value|
97
+ @attributes[v] = value
98
+ end
99
+
100
+ end
101
+ end
102
+
103
+ # Retrieve an Array of items (such as Contact, Message, etc)
104
+ def self.find(credentials, path, conditions = nil)
105
+ qbody = <<-QBODY
106
+ <D:searchrequest xmlns:D = "DAV:">
107
+ <D:sql>
108
+ #{conditions.nil? ? query(path) : search(path, conditions)}
109
+ </D:sql>
110
+ </D:searchrequest>
111
+ QBODY
112
+ items = []
113
+ DavSearchRequest.execute(credentials, :body => qbody) do |response|
114
+
115
+ xpath_query = "//a:propstat[a:status/text() = 'HTTP/1.1 200 OK']/a:prop"
116
+
117
+ Document.new(response.body).elements.each(xpath_query) do |m|
118
+ items << self.new(credentials, m)
119
+ end
120
+ end
121
+ items
122
+ end
123
+ end
124
+ end