timfel-active_cmis 0.3.1

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