lib: adds the mail submodule to curl
[nit.git] / lib / curl / curl.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Matthieu Lucas <lucasmatthieu@gmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Network functionnalities based on Curl_c module.
18 module curl
19
20 import curl_c
21 import mail
22
23 # Top level of Curl
24 class Curl
25 protected var prim_curl: CCurl
26
27 init
28 do
29 self.prim_curl = new CCurl.easy_init
30 assert curlInstance:self.prim_curl.is_init else
31 print "Curl must be instancied to be used"
32 end
33 end
34
35 # Check for correct initialization
36 fun is_ok: Bool do return self.prim_curl.is_init
37
38 # Get an HTTP Request object to perform your own
39 fun http_request(url: String): nullable CurlRequest
40 do
41 var err: CURLCode
42 err = self.prim_curl.easy_setopt(new CURLOption.follow_location, 1)
43 if not err.is_ok then return null
44
45 err = self.prim_curl.easy_setopt(new CURLOption.url, url)
46 if not err.is_ok then return null
47
48 return new CurlHTTPRequest(url, self)
49 end
50
51 # Release Curl instance
52 fun destroy do self.prim_curl.easy_clean
53 end
54
55 # CURL Request
56 class CurlRequest
57 super CCurlCallbacks
58
59 var url: String
60 var headers: nullable HeaderMap writable = null
61 var datas: nullable HeaderMap writable = null
62 var delegate: nullable CurlCallbacks writable = null
63 var verbose: Bool writable = false
64 private var curl: nullable Curl
65
66 # Launch request method
67 fun execute: CurlResponse is abstract
68
69 # Intern perform method, lowest level of request launching
70 private fun perform: nullable CurlResponse
71 do
72 if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
73
74 var err: CURLCode
75
76 if self.datas != null then
77 var postdatas = self.datas.to_url_encoded(self.curl.prim_curl)
78 err = self.curl.prim_curl.easy_setopt(new CURLOption.postfields, postdatas)
79 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
80 end
81
82 if self.headers != null then
83 var headers_joined = self.headers.join_pairs(": ")
84 err = self.curl.prim_curl.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist)
85 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
86 end
87
88 err = self.curl.prim_curl.easy_setopt(new CURLOption.verbose, self.verbose)
89 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
90
91 err = self.curl.prim_curl.easy_perform
92 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
93
94 return null
95 end
96
97 # Intern method with return a failed answer with given code and message
98 private fun answer_failure(error_code: Int, error_msg: String): CurlResponse
99 do
100 return new CurlResponseFailed(error_code, error_msg)
101 end
102 end
103
104 # CURL HTTP Request
105 class CurlHTTPRequest
106 super CurlRequest
107
108 # Execute HTTP request with settings configured through attribute
109 redef fun execute: CurlResponse
110 do
111 if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
112
113 var success_response: CurlResponseSuccess = new CurlResponseSuccess
114
115 var callback_receiver: CurlCallbacks = success_response
116 if self.delegate != null then callback_receiver = self.delegate.as(not null)
117
118 var err: CURLCode
119
120 err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header)
121 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
122
123 err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.body)
124 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
125
126 var err_resp = perform
127 if err_resp != null then return err_resp
128
129 var st_code = self.curl.prim_curl.easy_getinfo_long(new CURLInfoLong.response_code)
130 if not st_code == null then success_response.status_code = st_code.response
131
132 return success_response
133 end
134
135 # Download to file given resource
136 fun download_to_file(output_file_name: nullable String): CurlResponse
137 do
138 var success_response: CurlFileResponseSuccess = new CurlFileResponseSuccess
139
140 var callback_receiver: CurlCallbacks = success_response
141 if self.delegate != null then callback_receiver = self.delegate.as(not null)
142
143 var err: CURLCode
144 err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header)
145 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
146
147 err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.stream)
148 if not err.is_ok then return answer_failure(err.to_i, err.to_s)
149
150 var opt_name:nullable String
151 if not output_file_name == null then
152 opt_name = output_file_name
153 else if not self.url.substring(self.url.length-1, self.url.length) == "/" then
154 opt_name = self.url.basename("")
155 else
156 return answer_failure(0, "Unable to extract file name, please specify one")
157 end
158
159 success_response.i_file = new OFile.open(opt_name.to_cstring)
160 if not success_response.i_file.is_valid then
161 success_response.i_file.close
162 return answer_failure(0, "Unable to create associated file")
163 end
164
165 var err_resp = perform
166 if err_resp != null then return err_resp
167
168 var st_code = self.curl.prim_curl.easy_getinfo_long(new CURLInfoLong.response_code)
169 if not st_code == null then success_response.status_code = st_code.response
170
171 var speed = self.curl.prim_curl.easy_getinfo_double(new CURLInfoDouble.speed_download)
172 if not speed == null then success_response.speed_download = speed.response
173
174 var size = self.curl.prim_curl.easy_getinfo_double(new CURLInfoDouble.size_download)
175 if not size == null then success_response.size_download = size.response
176
177 var time = self.curl.prim_curl.easy_getinfo_double(new CURLInfoDouble.total_time)
178 if not time == null then success_response.total_time = time.response
179
180 success_response.i_file.close
181
182 return success_response
183 end
184 end
185
186 # Callbacks Interface, allow you to manage in your way the different streams
187 interface CurlCallbacks
188 super CCurlCallbacks
189 end
190
191 # Abstract Curl request response
192 abstract class CurlResponse
193 end
194
195 # Failed Response Class returned when errors during configuration are raised
196 class CurlResponseFailed
197 super CurlResponse
198
199 var error_code: Int
200 var error_msg: String
201
202 init (err_code: Int, err_msg: String)
203 do
204 self.error_code = err_code
205 self.error_msg = err_msg
206 end
207 end
208
209 # Success Abstract Response Success Class
210 abstract class CurlResponseSuccessIntern
211 super CurlCallbacks
212 super CurlResponse
213
214 var headers: HashMap[String, String] = new HashMap[String, String]
215 var status_code: Int = 0
216
217 # Receive headers from request due to headers callback registering
218 redef fun header_callback(line: String)
219 do
220 var splitted = line.split_with(':')
221 if splitted.length > 1 then
222 var key = splitted.shift
223 self.headers[key] = splitted.to_s
224 end
225 end
226 end
227
228 # Success Response Class of a basic response
229 class CurlResponseSuccess
230 super CurlResponseSuccessIntern
231
232 var body_str: String = ""
233
234 # Receive body from request due to body callback registering
235 redef fun body_callback(line: String)
236 do
237 self.body_str = "{self.body_str}{line}"
238 end
239 end
240
241 # Success Response Class of a downloaded File
242 class CurlFileResponseSuccess
243 super CurlResponseSuccessIntern
244
245 var speed_download: Int = 0
246 var size_download: Int = 0
247 var total_time: Int = 0
248 private var i_file: nullable OFile = null
249
250 # Receive bytes stream from request due to stream callback registering
251 redef fun stream_callback(buffer: String, size: Int, count: Int)
252 do
253 self.i_file.write(buffer, size, count)
254 end
255 end
256
257 # Pseudo map associating Strings to Strings,
258 # each key can have multiple associations
259 # and the order of insertion is important.
260 class HeaderMap
261 private var arr = new Array[Couple[String, String]]
262
263 fun []=(k, v: String) do arr.add(new Couple[String, String](k, v))
264
265 fun [](k: String): Array[String]
266 do
267 var res = new Array[String]
268 for c in arr do if c.first == k then res.add(c.second)
269 return res
270 end
271
272 fun iterate !each(k, v: String)
273 do
274 var i = arr.iterator
275 while i.is_ok do
276 var item = i.item
277 each(item.first, item.second)
278 i.next
279 end
280 end
281
282 # Convert Self to a single string used to post http fields
283 fun to_url_encoded(curl: CCurl): String
284 do
285 assert curlNotInitialized: curl.is_init else
286 print "to_url_encoded required a valid instance of CCurl Object."
287 end
288 var str: String = ""
289 var length = self.length
290 var i = 0
291 for k, v in self do
292 if k.length > 0 then
293 k = curl.escape(k)
294 v = curl.escape(v)
295 str = "{str}{k}={v}"
296 if i < length-1 then str = "{str}&"
297 end
298 i += 1
299 end
300 return str
301 end
302
303 # Concatenate couple of 'key value' separated by 'sep' in Array
304 fun join_pairs(sep: String): Array[String]
305 do
306 var col = new Array[String]
307 for k, v in self do col.add("{k}{sep}{v}")
308 return col
309 end
310
311 fun length: Int do return arr.length
312 fun is_empty: Bool do return arr.is_empty
313 end