sonar_rexchange 0.3.7

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.
@@ -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