timfel-active_cmis 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.
@@ -0,0 +1,222 @@
1
+
2
+ module ActiveCMIS
3
+ module Internal
4
+ class Connection
5
+ # @return [String, nil] The user that is used with the authentication to the server
6
+ attr_reader :user
7
+ # @return [Logger] A logger used to send debug and info messages
8
+ attr_reader :logger
9
+
10
+ # @param [Logger] Initialize with a logger of your choice
11
+ def initialize(logger)
12
+ @logger = logger || ActiveCMIS.default_logger
13
+ end
14
+
15
+ # Use authentication to access the CMIS repository
16
+ #
17
+ # @param method [Symbol] Currently only :basic is supported
18
+ # @param params The parameters that need to be sent to the Net::HTTP authentication method used, username and password for basic authentication
19
+ # @return [void]
20
+ # @example Basic authentication
21
+ # repo.authenticate(:basic, "username", "password")
22
+ # @example NTLM authentication
23
+ # repo.authenticate(:ntlm, "username", "password")
24
+ def authenticate(method, *params)
25
+ case method
26
+ when :basic, "basic"
27
+ @authentication = {:method => :basic_auth, :params => params}
28
+ @user = params.first
29
+ when :ntlm, "ntlm"
30
+ @authentication = {:method => :ntlm_auth, :params => params}
31
+ @user = params.first
32
+ else raise "Authentication method not supported"
33
+ end
34
+ end
35
+
36
+ # The return value is the unparsed body, unless an error occured
37
+ # If an error occurred, exceptions are thrown (see _ActiveCMIS::Exception
38
+ #
39
+ # @private
40
+ # @return [String] returns the body of the request, unless an error occurs
41
+ def get(url)
42
+ uri = normalize_url(url)
43
+
44
+ req = Net::HTTP::Get.new(uri.request_uri)
45
+ handle_request(uri, req)
46
+ end
47
+
48
+ # Does not throw errors, returns the full response (includes status code and headers)
49
+ # @private
50
+ # @return [Net::HTTP::Response]
51
+ def get_response(url)
52
+ logger.debug "GET (response) #{url}"
53
+ uri = normalize_url(url)
54
+
55
+ req = Net::HTTP::Get.new(uri.request_uri)
56
+ http = authenticate_request(uri, req)
57
+ response = http.request(req)
58
+ logger.debug "GOT (#{response.code}) #{url}"
59
+ response
60
+ end
61
+
62
+ # Returns the parsed body of the result
63
+ # @private
64
+ # @return [Nokogiri::XML::Document]
65
+ def get_xml(url)
66
+ Nokogiri::XML.parse(get(url), nil, nil, Nokogiri::XML::ParseOptions::STRICT)
67
+ end
68
+
69
+ # @private
70
+ # @return [Nokogiri::XML::Node]
71
+ def get_atom_entry(url)
72
+ # FIXME: add validation that first child is really an entry
73
+ get_xml(url).child
74
+ end
75
+
76
+ # @private
77
+ def put(url, body, headers = {})
78
+ uri = normalize_url(url)
79
+
80
+ req = Net::HTTP::Put.new(uri.request_uri)
81
+ headers.each {|k,v| req.add_field k, v}
82
+ assign_body(req, body)
83
+ handle_request(uri, req)
84
+ end
85
+
86
+ # @private
87
+ def delete(url, headers = {})
88
+ uri = normalize_url(url)
89
+
90
+ req = Net::HTTP::Put.new(uri.request_uri)
91
+ headers.each {|k,v| req.add_field k, v}
92
+ handle_request(uri, req)
93
+ end
94
+
95
+ # @private
96
+ def post(url, body, headers = {})
97
+ uri = normalize_url(url)
98
+
99
+ req = Net::HTTP::Post.new(uri.request_uri)
100
+ headers.each {|k,v| req.add_field k, v}
101
+ assign_body(req, body)
102
+ handle_request(uri, req)
103
+ end
104
+
105
+ # Does not throw errors, returns the full response (includes status code and headers)
106
+ # @private
107
+ def post_response(url, body, headers = {})
108
+ logger.debug "POST (response) #{url}"
109
+ uri = normalize_url(url)
110
+
111
+ req = Net::HTTP::Post.new(uri.request_uri)
112
+ headers.each {|k,v| req.add_field k, v}
113
+ assign_body(req, body)
114
+
115
+ http = authenticate_request(uri, req)
116
+ response = http.request(req)
117
+ logger.debug "POSTED (#{response.code}) #{url}"
118
+ response
119
+ end
120
+
121
+ # @private
122
+ def delete(url)
123
+ uri = normalize_url(url)
124
+
125
+ req = Net::HTTP::Delete.new(uri.request_uri)
126
+ handle_request(uri, req)
127
+ end
128
+
129
+ private
130
+ def normalize_url(url)
131
+ case url
132
+ when URI; url
133
+ else URI.parse(url.to_s)
134
+ end
135
+ end
136
+
137
+ def http_class
138
+ @http_class ||= begin
139
+ if proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] then
140
+ p_uri = URI.parse(proxy)
141
+ p_user, p_pass = p_uri.user, p_uri.password if p_uri.user
142
+ Net::HTTP::Proxy(p_uri.host, p_uri.port, p_user, p_pass)
143
+ else
144
+ Net::HTTP
145
+ end
146
+ end
147
+ end
148
+
149
+ def authenticate_request(uri, req)
150
+ http = http_class.new(uri.host, uri.port)
151
+ if uri.scheme == 'https'
152
+ http.use_ssl = true
153
+ end
154
+ if auth = @authentication
155
+ req.send(auth[:method], *auth[:params])
156
+ end
157
+ http
158
+ end
159
+
160
+ def assign_body(req, body)
161
+ if body.respond_to? :length
162
+ req.body = body
163
+ else
164
+ req.body_stream = body
165
+ if body.respond_to? :stat
166
+ req["Content-Length"] = body.stat.size.to_s
167
+ elsif req["Content-Size"].nil?
168
+ req["Transfer-Encoding"] = 'chunked'
169
+ end
170
+ end
171
+ end
172
+
173
+ def handle_request(uri, req, retry_count = 0)
174
+ logger.debug "#{req.method} #{uri}"
175
+ http = authenticate_request(uri, req)
176
+
177
+ status, body, headers = nil
178
+ http.request(req) { |resp|
179
+ status = resp.code.to_i
180
+ body = resp.body
181
+ headers = resp
182
+ }
183
+
184
+ logger.debug "RECEIVED #{status}"
185
+
186
+ if 200 <= status && status < 300
187
+ return body
188
+ elsif 300 <= status && status < 400
189
+ # follow the redirected a limited number of times
190
+ location = headers["location"]
191
+ logger.debug "REDIRECTING: #{location.inspect}"
192
+ if retry_count <= 3
193
+ new_uri = URI.parse(location)
194
+ if new_uri.relative?
195
+ new_uri = uri + location
196
+ end
197
+ new_req = req.class.new(uri.request_uri)
198
+ handle_request(new_uri, new_req, retry_count + 1)
199
+ else
200
+ raise HTTPError.new("Too many redirects")
201
+ end
202
+ elsif 400 <= status && status < 500
203
+ # Problem: some codes 400, 405, 403, 409, 500 have multiple meanings
204
+ logger.error "Error occurred when handling request:\n#{body}"
205
+ case status
206
+ when 400; raise Error::InvalidArgument.new(body)
207
+ # FIXME: can also be filterNotValid
208
+ when 401; raise HTTPError::AuthenticationError.new(body)
209
+ when 404; raise Error::ObjectNotFound.new(body)
210
+ when 403; raise Error::PermissionDenied.new(body)
211
+ # FIXME: can also be streamNotSupported (?? shouldn't that be 405??)
212
+ when 405; raise Error::NotSupported.new(body)
213
+ else
214
+ raise HTTPError::ClientError.new("A HTTP #{status} error occured, for more precision update the code:\n" + body)
215
+ end
216
+ elsif 500 <= status
217
+ raise HTTPError::ServerError.new("The server encountered an internal error #{status} (this could be a client error though):\n" + body)
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,82 @@
1
+ module ActiveCMIS
2
+ module Internal
3
+ # @private
4
+ module Utils
5
+ # @private
6
+ def self.escape_url_parameter(parameter)
7
+ control = "\x00-\x1F\x7F"
8
+ space = " "
9
+ delims = "<>#%\""
10
+ unwise = '{}|\\\\^\[\]`'
11
+ query = ";/?:@&=+,$"
12
+ escape(parameter, /[#{control+space+delims+unwise+query}]/o)
13
+ end
14
+
15
+ # Given an url (string or URI) returns that url with the given parameters appended
16
+ #
17
+ # This method does not perform any encoding on the paramter or key values.
18
+ # This method does not check the existing parameters for duplication in keys
19
+ # @private
20
+ def self.append_parameters(uri, parameters)
21
+ uri = case uri
22
+ when String; string = true; URI.parse(uri)
23
+ when URI; uri.dup
24
+ end
25
+ uri.query = [uri.query, *parameters.map {|key, value| "#{key}=#{value}"} ].compact.join "&"
26
+ if string
27
+ uri.to_s
28
+ else
29
+ uri
30
+ end
31
+ end
32
+
33
+ # FIXME?? percent_encode and escape_url_parameter serve nearly the same purpose, replace one?
34
+ # @private
35
+ def self.percent_encode(string)
36
+ escape(string, /[^#{URI::PATTERN::UNRESERVED}]/o)
37
+ end
38
+
39
+ def self.escape(string, pattern)
40
+ if defined?(URI::Parser)
41
+ parser = URI::Parser.new
42
+ parser.escape(string, pattern)
43
+ else
44
+ URI.escape(string, pattern)
45
+ end
46
+ end
47
+
48
+ # Returns id if id is already an object, object_by_id if id is a string, nil otherwise
49
+ # @private
50
+ def self.string_or_id_to_object(repository, id)
51
+ # FIXME: only used in lib/activecmis/relationship.rb, the repository parameter
52
+ # would be unnecessary if included.
53
+ # Should this be a generic method, or should this be moved to the Relationship class?
54
+ # Or should I start including this module in every place that needs it?
55
+ case id
56
+ when String; repository.object_by_id(id)
57
+ when ::ActiveCMIS::Object; id
58
+ end
59
+ end
60
+
61
+ # @private
62
+ def self.extract_links(xml, rel, type_main = nil, type_params = {})
63
+ links = xml.xpath("at:link[@rel = '#{rel}']", NS::COMBINED)
64
+
65
+ if type_main
66
+ type_main = Regexp.escape(type_main)
67
+ if type_params.empty?
68
+ regex = /#{type_main}/
69
+ else
70
+ parameters = type_params.map {|k,v| "#{Regexp.escape(k)}=#{Regexp.escape(v)}" }.join(";\s*")
71
+ regex = /#{type_main};\s*#{parameters}/
72
+ end
73
+ links = links.select do |node|
74
+ regex === node.attribute("type").to_s
75
+ end
76
+ end
77
+
78
+ links.map {|l| l.attribute("href").to_s}
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveCMIS
2
+ # This module defines namespaces that often occur in the REST/Atompub API to CMIS
3
+ module NS
4
+ CMIS_CORE = "http://docs.oasis-open.org/ns/cmis/core/200908/"
5
+ CMIS_REST = "http://docs.oasis-open.org/ns/cmis/restatom/200908/"
6
+ CMIS_MESSAGING = "http://docs.oasis-open.org/ns/cmis/messaging/200908/"
7
+ APP = "http://www.w3.org/2007/app"
8
+ ATOM = "http://www.w3.org/2005/Atom"
9
+
10
+ COMBINED = {
11
+ "xmlns:c" => CMIS_CORE,
12
+ "xmlns:cra" => CMIS_REST,
13
+ "xmlns:cm" => CMIS_MESSAGING,
14
+ "xmlns:app" => APP,
15
+ "xmlns:at" => ATOM
16
+ }
17
+ end
18
+ end