module curl
import curl_c
-import mail
# Top level of Curl
class Curl
- protected var prim_curl: CCurl
+ protected var prim_curl = new CCurl.easy_init
init
do
- self.prim_curl = new CCurl.easy_init
assert curlInstance:self.prim_curl.is_init else
print "Curl must be instancied to be used"
end
# Check for correct initialization
fun is_ok: Bool do return self.prim_curl.is_init
- # Get an HTTP Request object to perform your own
- fun http_request(url: String): nullable CurlRequest
- do
- var err: CURLCode
- err = self.prim_curl.easy_setopt(new CURLOption.follow_location, 1)
- if not err.is_ok then return null
-
- err = self.prim_curl.easy_setopt(new CURLOption.url, url)
- if not err.is_ok then return null
-
- return new CurlHTTPRequest(url, self)
- end
-
# Release Curl instance
fun destroy do self.prim_curl.easy_clean
end
# CURL Request
class CurlRequest
- super CCurlCallbacks
- var url: String
- var headers: nullable HeaderMap writable = null
- var datas: nullable HeaderMap writable = null
- var delegate: nullable CurlCallbacks writable = null
- var verbose: Bool writable = false
- private var curl: nullable Curl
+ var verbose: Bool = false is writable
+ private var curl: nullable Curl = null
# Launch request method
fun execute: CurlResponse is abstract
# Intern perform method, lowest level of request launching
- private fun perform: nullable CurlResponse
+ private fun perform: nullable CurlResponseFailed
do
if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
- var err: CURLCode
-
- if self.datas != null then
- var postdatas = self.datas.to_url_encoded(self.curl.prim_curl)
- err = self.curl.prim_curl.easy_setopt(new CURLOption.postfields, postdatas)
- if not err.is_ok then return answer_failure(err.to_i, err.to_s)
- end
-
- if self.headers != null then
- var headers_joined = self.headers.join_pairs(": ")
- err = self.curl.prim_curl.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist)
- if not err.is_ok then return answer_failure(err.to_i, err.to_s)
- end
+ var err
err = self.curl.prim_curl.easy_setopt(new CURLOption.verbose, self.verbose)
if not err.is_ok then return answer_failure(err.to_i, err.to_s)
end
# Intern method with return a failed answer with given code and message
- private fun answer_failure(error_code: Int, error_msg: String): CurlResponse
+ private fun answer_failure(error_code: Int, error_msg: String): CurlResponseFailed
do
return new CurlResponseFailed(error_code, error_msg)
end
# CURL HTTP Request
class CurlHTTPRequest
super CurlRequest
+ super CCurlCallbacks
+ super CurlCallbacksRegisterIntern
+
+ var url: String
+ var datas: nullable HeaderMap = null is writable
+ var headers: nullable HeaderMap = null is writable
+
+ # Set the user agent for all following HTTP requests
+ fun user_agent=(name: String)
+ do
+ curl.prim_curl.easy_setopt(new CURLOption.user_agent, name)
+ end
+
+ init (url: String, curl: nullable Curl) is old_style_init do
+ self.url = url
+ self.curl = curl
+ end
# Execute HTTP request with settings configured through attribute
- redef fun execute: CurlResponse
+ redef fun execute
do
if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
- var success_response: CurlResponseSuccess = new CurlResponseSuccess
-
+ var success_response = new CurlResponseSuccess
var callback_receiver: CurlCallbacks = success_response
if self.delegate != null then callback_receiver = self.delegate.as(not null)
- var err: CURLCode
+ var err
+
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.url, url)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ # Callbacks
err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header)
if not err.is_ok then return answer_failure(err.to_i, err.to_s)
err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.body)
if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ # HTTP Header
+ if self.headers != null then
+ var headers_joined = self.headers.join_pairs(": ")
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ end
+
+ # Datas
+ if self.datas != null then
+ var postdatas = self.datas.to_url_encoded(self.curl.prim_curl)
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.postfields, postdatas)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ end
+
var err_resp = perform
if err_resp != null then return err_resp
# Download to file given resource
fun download_to_file(output_file_name: nullable String): CurlResponse
do
- var success_response: CurlFileResponseSuccess = new CurlFileResponseSuccess
+ var success_response = new CurlFileResponseSuccess
var callback_receiver: CurlCallbacks = success_response
if self.delegate != null then callback_receiver = self.delegate.as(not null)
- var err: CURLCode
+ var err
+
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.url, url)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header)
if not err.is_ok then return answer_failure(err.to_i, err.to_s)
err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.stream)
if not err.is_ok then return answer_failure(err.to_i, err.to_s)
- var opt_name:nullable String
+ var opt_name
if not output_file_name == null then
opt_name = output_file_name
else if not self.url.substring(self.url.length-1, self.url.length) == "/" then
end
end
+# CURL Mail Request
+class CurlMailRequest
+ super CurlRequest
+ super CCurlCallbacks
+
+ var headers: nullable HeaderMap = null is writable
+ var headers_body: nullable HeaderMap = null is writable
+ var from: nullable String = null is writable
+ var to: nullable Array[String] = null is writable
+ var cc: nullable Array[String] = null is writable
+ var bcc: nullable Array[String] = null is writable
+ var subject: nullable String = "" is writable
+ var body: nullable String = "" is writable
+ private var supported_outgoing_protocol: Array[String] = ["smtp", "smtps"]
+
+ init (curl: nullable Curl) is old_style_init do
+ self.curl = curl
+ end
+
+ # Helper method to add conventional space while building entire mail
+ private fun add_conventional_space(str: String):String do return "{str}\n" end
+
+ # Helper method to add pair values to mail content while building it (ex: "To:", "address@mail.com")
+ private fun add_pair_to_content(str: String, att: String, val: nullable String):String
+ do
+ if val != null then return "{str}{att}{val}\n"
+ return "{str}{att}\n"
+ end
+
+ # Helper method to add entire list of pairs to mail content
+ private fun add_pairs_to_content(content: String, pairs: HeaderMap):String
+ do
+ for h_key, h_val in pairs do content = add_pair_to_content(content, h_key, h_val)
+ return content
+ end
+
+ # Check for host and protocol availability
+ private fun is_supported_outgoing_protocol(host: String):CURLCode
+ do
+ var host_reach = host.split_with("://")
+ if host_reach.length > 1 and supported_outgoing_protocol.has(host_reach[0]) then return once new CURLCode.ok
+ return once new CURLCode.unsupported_protocol
+ end
+
+ # Configure server host and user credentials if needed.
+ fun set_outgoing_server(host: String, user: nullable String, pwd: nullable String): nullable CurlResponseFailed
+ do
+ # Check Curl initialisation
+ if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
+
+ var err
+
+ # Host & Protocol
+ err = is_supported_outgoing_protocol(host)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.url, host)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ # Credentials
+ if not user == null and not pwd == null then
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.username, user)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.password, pwd)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ end
+
+ return null
+ end
+
+ # Execute Mail request with settings configured through attribute
+ redef fun execute
+ do
+ if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
+
+ var success_response = new CurlMailResponseSuccess
+ var content = ""
+ # Headers
+ if self.headers != null then
+ content = add_pairs_to_content(content, self.headers.as(not null))
+ end
+
+ # Recipients
+ var g_rec = new Array[String]
+ if self.to != null and self.to.length > 0 then
+ content = add_pair_to_content(content, "To:", self.to.join(","))
+ g_rec.append(self.to.as(not null))
+ end
+ if self.cc != null and self.cc.length > 0 then
+ content = add_pair_to_content(content, "Cc:", self.cc.join(","))
+ g_rec.append(self.cc.as(not null))
+ end
+ if self.bcc != null and self.bcc.length > 0 then g_rec.append(self.bcc.as(not null))
+
+ if g_rec.length < 1 then return answer_failure(0, "The mail recipients can not be empty")
+
+ var err
+
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.mail_rcpt, g_rec.to_curlslist)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ # From
+ if not self.from == null then
+ content = add_pair_to_content(content, "From:", self.from)
+ err = self.curl.prim_curl.easy_setopt(new CURLOption.mail_from, self.from.as(not null))
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ end
+
+ # Subject
+ content = add_pair_to_content(content, "Subject:", self.subject)
+
+ # Headers body
+ if self.headers_body != null then
+ content = add_pairs_to_content(content, self.headers_body.as(not null))
+ end
+
+ # Body
+ content = add_conventional_space(content)
+ content = add_pair_to_content(content, "", self.body)
+ content = add_conventional_space(content)
+ err = self.curl.prim_curl.register_callback(self, once new CURLCallbackType.read)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+ err = self.curl.prim_curl.register_read_datas_callback(self, content)
+ if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+
+ var err_resp = perform
+ if err_resp != null then return err_resp
+
+ return success_response
+ end
+end
+
# Callbacks Interface, allow you to manage in your way the different streams
interface CurlCallbacks
super CCurlCallbacks
end
+# Callbacks attributes
+abstract class CurlCallbacksRegisterIntern
+ var delegate: nullable CurlCallbacks = null is writable
+end
+
# Abstract Curl request response
abstract class CurlResponse
end
var error_code: Int
var error_msg: String
-
- init (err_code: Int, err_msg: String)
- do
- self.error_code = err_code
- self.error_msg = err_msg
- end
end
# Success Abstract Response Success Class
super CurlCallbacks
super CurlResponse
- var headers: HashMap[String, String] = new HashMap[String, String]
- var status_code: Int = 0
+ var headers = new HashMap[String, String]
# Receive headers from request due to headers callback registering
- redef fun header_callback(line: String)
+ redef fun header_callback(line)
do
var splitted = line.split_with(':')
if splitted.length > 1 then
class CurlResponseSuccess
super CurlResponseSuccessIntern
- var body_str: String = ""
+ var body_str = ""
+ var status_code = 0
# Receive body from request due to body callback registering
redef fun body_callback(line: String)
end
end
+# Success Response Class of mail request
+class CurlMailResponseSuccess
+ super CurlResponseSuccessIntern
+end
+
# Success Response Class of a downloaded File
class CurlFileResponseSuccess
super CurlResponseSuccessIntern
- var speed_download: Int = 0
- var size_download: Int = 0
- var total_time: Int = 0
+ var status_code = 0
+ var speed_download = 0
+ var size_download = 0
+ var total_time = 0
private var i_file: nullable OFile = null
# Receive bytes stream from request due to stream callback registering
- redef fun stream_callback(buffer: String, size: Int, count: Int)
+ redef fun stream_callback(buffer, size, count)
do
self.i_file.write(buffer, size, count)
end
return res
end
- fun iterate !each(k, v: String)
- do
- var i = arr.iterator
- while i.is_ok do
- var item = i.item
- each(item.first, item.second)
- i.next
- end
- end
+ fun iterator: MapIterator[String, String] do return new HeaderMapIterator(self)
# Convert Self to a single string used to post http fields
fun to_url_encoded(curl: CCurl): String
assert curlNotInitialized: curl.is_init else
print "to_url_encoded required a valid instance of CCurl Object."
end
- var str: String = ""
+ var str = ""
var length = self.length
var i = 0
for k, v in self do
fun length: Int do return arr.length
fun is_empty: Bool do return arr.is_empty
end
+
+class HeaderMapIterator
+ super MapIterator[String, String]
+
+ private var iterator: Iterator[Couple[String, String]]
+ init(map: HeaderMap) is old_style_init do self.iterator = map.arr.iterator
+
+ redef fun is_ok do return self.iterator.is_ok
+ redef fun next do self.iterator.next
+ redef fun item do return self.iterator.item.second
+ redef fun key do return self.iterator.item.first
+end