Data transfer powered by the native curl library

Download or upload data over HTTP with CurlHTTPRequest and send emails with CurlMail. Scripts can use the easier (but limited) services on Text, http_get and http_download, provided by extra.

Introduced classes

private class Curl

curl :: Curl

Curl library handle
interface CurlCallbacks

curl :: CurlCallbacks

Callbacks Interface, allow you to manage in your way the different streams
class CurlFileResponseSuccess

curl :: CurlFileResponseSuccess

Success Response Class of a downloaded File
class CurlHTTPRequest

curl :: CurlHTTPRequest

HTTP request builder
class CurlMail

curl :: CurlMail

CURL Mail Request
class CurlRequest

curl :: CurlRequest

CURL Request
abstract class CurlResponse

curl :: CurlResponse

Abstract Curl request response
class CurlResponseFailed

curl :: CurlResponseFailed

Failed Response Class returned when errors during configuration are raised
class CurlResponseSuccess

curl :: CurlResponseSuccess

Success Response Class of a basic response
abstract class CurlResponseSuccessIntern

curl :: CurlResponseSuccessIntern

Success Abstract Response Success Class
class HeaderMap

curl :: HeaderMap

Pseudo map associating String to String for HTTP exchanges

All class definitions

private class Curl

curl $ Curl

Curl library handle
interface CurlCallbacks

curl $ CurlCallbacks

Callbacks Interface, allow you to manage in your way the different streams
class CurlFileResponseSuccess

curl $ CurlFileResponseSuccess

Success Response Class of a downloaded File
class CurlHTTPRequest

curl $ CurlHTTPRequest

HTTP request builder
class CurlMail

curl $ CurlMail

CURL Mail Request
class CurlRequest

curl $ CurlRequest

CURL Request
abstract class CurlResponse

curl $ CurlResponse

Abstract Curl request response
class CurlResponseFailed

curl $ CurlResponseFailed

Failed Response Class returned when errors during configuration are raised
class CurlResponseSuccess

curl $ CurlResponseSuccess

Success Response Class of a basic response
abstract class CurlResponseSuccessIntern

curl $ CurlResponseSuccessIntern

Success Abstract Response Success Class
class HeaderMap

curl $ HeaderMap

Pseudo map associating String to String for HTTP exchanges
package_diagram curl::curl curl curl::native_curl native_curl curl::curl->curl::native_curl core core curl::native_curl->core ...core ... ...core->core curl::extra extra curl::extra->curl::curl curl::curl_http curl_http curl::curl_http->curl::curl curl::curl_rest curl_rest curl::curl_rest->curl::curl github::api api github::api->curl::curl linux::http_request http_request linux::http_request->curl::curl neo4j::curl_json curl_json neo4j::curl_json->curl::curl nitcorn::proxy proxy nitcorn::proxy->curl::curl nlp::stanford stanford nlp::stanford->curl::curl nitc::nitpm nitpm nitc::nitpm->curl::curl a_star-m a_star-m a_star-m->curl::extra a_star-m->curl::curl_http a_star-m->curl::curl_rest a_star-m->linux::http_request a_star-m->nitc::nitpm a_star-m... ... a_star-m...->a_star-m github::cache cache github::cache->github::api github::events events github::events->github::api github::cache... ... github::cache...->github::cache github::events... ... github::events...->github::events neo4j::neo4j neo4j neo4j::neo4j->neo4j::curl_json neo4j::neo4j... ... neo4j::neo4j...->neo4j::neo4j nitcorn::nitcorn_reverse_proxy nitcorn_reverse_proxy nitcorn::nitcorn_reverse_proxy->nitcorn::proxy nitcorn::nitcorn_reverse_proxy... ... nitcorn::nitcorn_reverse_proxy...->nitcorn::nitcorn_reverse_proxy nlp::nlp nlp nlp::nlp->nlp::stanford nlp::nlp... ... nlp::nlp...->nlp::nlp

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module native_curl

curl :: native_curl

Binding of C libCurl which allow us to interact with network.

Children

module api

github :: api

Nit object oriented interface to Github api.
module curl_http

curl :: curl_http

Example use of the Curl module
module curl_json

neo4j :: curl_json

cURL requests compatible with the JSON REST APIs.
module curl_rest

curl :: curl_rest

module extra

curl :: extra

Shortcut services for scripts: http_get and http_download
module http_request

linux :: http_request

Implementation of app::http_request using GDK and Curl
module nitpm

nitc :: nitpm

Nit package manager command line interface
module proxy

nitcorn :: proxy

Provides the ProxyAction action, which redirects requests to another interface
module stanford

nlp :: stanford

Natural Language Processor based on the StanfordNLP core.

Descendants

module a_star-m

a_star-m

module api

nitc :: api

Components required to build a web server about the nit model.
module api_auth

nitc :: api_auth

module api_feedback

nitc :: api_feedback

Feedback related features
module cache

github :: cache

Enable caching on Github API accesses.
module events

github :: events

Events are emitted by Github Hooks.
module github

github :: github

Nit wrapper for Github API
module graph

neo4j :: graph

Provides an interface for services on a Neo4j graphs.
module hooks

github :: hooks

Github hook event listening with nitcorn.
module json_graph_store

neo4j :: json_graph_store

Provides JSON as a mean to store graphs.
module loader

github :: loader

module neo

nitc :: neo

Save and load a Model to/from a Neo4j graph.
module neo4j

neo4j :: neo4j

Neo4j connector through its JSON REST API using curl.
module nitcorn_reverse_proxy

nitcorn :: nitcorn_reverse_proxy

Minimal example using a ProxyAction
module nitweb

nitc :: nitweb

Runs a webserver based on nitcorn that render things from model.
module nlp

nlp :: nlp

Natural Language Processor based on the StanfordNLP core.
module nlp_index

nlp :: nlp_index

Example showing how to use a NLPFileIndex.
module pop_auth

popcorn :: pop_auth

Authentification handlers.
module sequential_id

neo4j :: sequential_id

Provides a sequential identification scheme for Neo4j nodes.
module test_neo

nitc :: test_neo

Test for neo model saving and loading.
module wallet

github :: wallet

Github OAuth tokens management
# Data transfer powered by the native curl library
#
# Download or upload data over HTTP with `CurlHTTPRequest` and send emails
# with `CurlMail`. Scripts can use the easier (but limited) services on `Text`,
# `http_get` and `http_download`, provided by `curl::extra`.
module curl

import native_curl

# Curl library handle
private class Curl
	super FinalizableOnce

	var native = new NativeCurl.easy_init

	# Is this instance correctly initialized?
	fun is_ok: Bool do return self.native.is_init

	redef fun finalize_once do if is_ok then native.easy_clean
end

# CURL Request
class CurlRequest

	private var curl = new Curl

	# Shall this request be verbose?
	var verbose: Bool = false is writable

	# Intern perform method, lowest level of request launching
	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

		err = self.curl.native.easy_setopt(new CURLOption.verbose, self.verbose)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		err = self.curl.native.easy_perform
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		return null
	end

	# Intern method with return a failed answer with given code and message
	private fun answer_failure(error_code: Int, error_msg: String): CurlResponseFailed
	do
		return new CurlResponseFailed(error_code, error_msg)
	end

	# Close low-level resources associated to this request
	#
	# Once closed, this request can't be used again.
	#
	# If this service isn't called explicitly, low-level resources
	# may be freed automatically by the GC.
	fun close do curl.finalize
end

# HTTP request builder
#
# The request itself is sent by either `execute` or `download_to_file`.
# The attributes of this class must be set before calling either of these two methods.
#
# ## Minimal usage example
#
# ~~~
# var request = new CurlHTTPRequest("http://example.org/")
# var response = request.execute
# if response isa CurlResponseSuccess then
#     print "Response status code: {response.status_code}"
#     print response.body_str
# else if response isa CurlResponseFailed then
#     print_error response.error_msg
# end
# ~~~
class CurlHTTPRequest
	super CurlRequest
	super NativeCurlCallbacks

	# Address of the remote resource to request
	var url: String

	# Data for the body of a POST request
	var data: nullable HeaderMap is writable

	# Raw body string
	#
	# Set this value to send raw data instead of the POST formatted `data`.
	#
	# If `data` is set, the body will not be sent.
	var body: nullable String is writable

	# Header content of the request
	var headers: nullable HeaderMap is writable

	# Delegates to customize the behavior when running `execute`
	var delegate: nullable CurlCallbacks is writable

	# Set the user agent for all following HTTP requests
	var user_agent: nullable String is writable

	# Set the Unix domain socket path to use
	#
	# When not null, enables using a Unix domain socket
	# instead of a TCP connection and DNS hostname resolution.
	var unix_socket_path: nullable String is writable

	# The HTTP method, GET by default
	#
	# Must be a capitalized string with request name complying with RFC7231
	var method: String = "GET" is optional, writable

	# Execute HTTP request
	#
	# By default, the response body is returned in an instance of `CurlResponse`.
	# This behavior can be customized by setting a custom `delegate`.
	fun execute: CurlResponse
	do
		# Reset libcurl parameters as the lib is shared and options
		# might affect requests from one another.
		if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")

		var success_response = new CurlResponseSuccess
		var callback_receiver: CurlCallbacks = success_response
		var err : CURLCode

		# Prepare request
		err = prepare_request(callback_receiver)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		# Perform request
		var err_resp = perform
		if err_resp != null then return err_resp

		var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code)
		if not st_code == null then success_response.status_code = st_code

		return success_response
	end

	# Internal function that sets cURL options and request' parameters
	private fun prepare_request(callback_receiver: CurlCallbacks) : CURLCode
	do
		var err

		# cURL options and delegates
		err = set_curl_options
		if not err.is_ok then return err

		# Callbacks
		err = set_curl_callback(callback_receiver)
		if not err.is_ok then return err

		# HTTP Header
		err = set_curl_http_header
		if not err.is_ok then return err

		# Set HTTP method and body
		err = set_method
		if not err.is_ok then return err
		err = set_body

		return err
	end

	# Set cURL parameters according to assigned HTTP method set in method
	# attribute and body if the method allows it according to RFC7231
	private fun set_method : CURLCode
	do
		var err : CURLCode

		if self.method=="GET" then
			err=self.curl.native.easy_setopt(new CURLOption.get, 1)

		else if self.method=="POST" then
			err=self.curl.native.easy_setopt(new CURLOption.post, 1)

		else if self.method=="HEAD" then
			err=self.curl.native.easy_setopt(new CURLOption.no_body,1)

		else
			err=self.curl.native.easy_setopt(new CURLOption.custom_request,self.method)
		end
		return err
	end

	# Set request's body
	private fun set_body : CURLCode
	do
		var err
		var data = self.data
		var body = self.body

		if data != null then
			var postdatas = data.to_url_encoded(self.curl)
			err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas)
			if not err.is_ok then return err
		else if body != null then
			err = self.curl.native.easy_setopt(new CURLOption.postfields, body)
			if not err.is_ok then return err
		end
		return new CURLCode.ok
	end

	# Set cURL options
	# such as delegate, follow location, URL, user agent and address family
	private fun set_curl_options : CURLCode
	do
		var err

		err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1)
		if not err.is_ok then return err

		err = self.curl.native.easy_setopt(new CURLOption.url, url)
		if not err.is_ok then return err

		var user_agent = user_agent
		if user_agent != null then
			err = curl.native.easy_setopt(new CURLOption.user_agent, user_agent)
			if not err.is_ok then return err
		end

		var unix_socket_path = unix_socket_path
		if unix_socket_path != null then
			err = self.curl.native.easy_setopt(new CURLOption.unix_socket_path, unix_socket_path)
			if not err.is_ok then return err
		end
		return err
	end

	# Set cURL callback
	private fun set_curl_callback(callback_receiver : CurlCallbacks) : CURLCode
	do
		var err

		if self.delegate != null then callback_receiver = self.delegate.as(not null)

		err = self.curl.native.register_callback_header(callback_receiver)
		if not err.is_ok then return err

		err = self.curl.native.register_callback_body(callback_receiver)
		if not err.is_ok then return err

		return err
	end

	# Set cURL request header according to attribute headers
	private fun set_curl_http_header : CURLCode
	do
		var headers = self.headers
		if headers != null then
			var headers_joined = headers.join_pairs(": ")
			var err = self.curl.native.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist)
			if not err.is_ok then return err
		end
		return new CURLCode.ok
	end

	# Download to file given resource
	fun download_to_file(output_file_name: nullable String): CurlResponse
	do
		if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")

		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

		err = self.curl.native.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.native.easy_setopt(new CURLOption.url, url)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		err = self.curl.native.register_callback_header(callback_receiver)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		err = self.curl.native.register_callback_stream(callback_receiver)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		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
			opt_name = self.url.basename
		else
			return answer_failure(0, "Unable to extract file name, please specify one")
		end

		success_response.file = new FileWriter.open(opt_name)
		if not success_response.file.is_writable then
			return answer_failure(0, "Unable to create associated file")
		end

		var err_resp = perform
		if err_resp != null then return err_resp

		var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code)
		if not st_code == null then success_response.status_code = st_code

		var speed = self.curl.native.easy_getinfo_double(new CURLInfoDouble.speed_download)
		if not speed == null then success_response.speed_download = speed

		var size = self.curl.native.easy_getinfo_double(new CURLInfoDouble.size_download)
		if not size == null then success_response.size_download = size

		var time = self.curl.native.easy_getinfo_double(new CURLInfoDouble.total_time)
		if not time == null then success_response.total_time = time

		success_response.file.close

		return success_response
	end
end


# CURL Mail Request
#
# ~~~
# # Craft mail
# var mail = new CurlMail("sender@example.org",
#                            to=["to@example.org"], cc=["bob@example.org"])
#
# mail.headers_body["Content-Type:"] = """text/html; charset="UTF-8""""
# mail.headers_body["Content-Transfer-Encoding:"] = "quoted-printable"
#
# mail.body = "<h1>Here you can write HTML stuff.</h1>"
# mail.subject = "Hello From My Nit Program"
#
# # Set mail server
# var error = mail.set_outgoing_server("smtps://smtp.example.org:465",
#                                      "user@example.org", "mypassword")
# if error != null then
#     print "Mail Server Error: {error}"
#     exit 0
# end
#
# # Send
# error = mail.execute
# if error != null then
#     print "Transfer Error: {error}"
#     exit 0
# end
# ~~~
class CurlMail
	super CurlRequest
	super NativeCurlCallbacks

	# Address of the sender
	var from: nullable String is writable

	# Main recipients
	var to: nullable Array[String] is writable

	# Subject line
	var subject: nullable String is writable

	# Text content
	var body: nullable String is writable

	# CC recipients
	var cc: nullable Array[String] is writable

	# BCC recipients (hidden from other recipients)
	var bcc: nullable Array[String] is writable

	# HTTP header
	var headers = new HeaderMap is lazy, writable

	# Content header
	var headers_body = new HeaderMap is lazy, writable

	# Protocols supported to send mail to a server
	#
	# Default value at `["smtp", "smtps"]`
	var supported_outgoing_protocol = ["smtp", "smtps"]

	# 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.native.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.native.easy_setopt(new CURLOption.username, user)
			if not err.is_ok then return answer_failure(err.to_i, err.to_s)

			err = self.curl.native.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
	fun execute: nullable CurlResponseFailed
	do
		if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")

		var lines = new Array[String]

		# Headers
		var headers = self.headers
		if not headers.is_empty then
			for k, v in headers do lines.add "{k}{v}"
		end

		# Recipients
		var all_recipients = new Array[String]
		var to = self.to
		if to != null and to.length > 0 then
			lines.add "To:{to.join(",")}"
			all_recipients.append to
		end

		var cc = self.cc
		if cc != null and cc.length > 0 then
			lines.add "Cc:{cc.join(",")}"
			all_recipients.append cc
		end

		var bcc = self.bcc
		if bcc != null and bcc.length > 0 then all_recipients.append bcc

		if all_recipients.is_empty then return answer_failure(0, "There must be at lease one recipient")

		var err = self.curl.native.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.native.easy_setopt(new CURLOption.mail_rcpt, all_recipients.to_curlslist)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		# From
		var from = self.from
		if not from == null then
			lines.add "From:{from}"

			err = self.curl.native.easy_setopt(new CURLOption.mail_from, from)
			if not err.is_ok then return answer_failure(err.to_i, err.to_s)
		end

		# Subject
		var subject = self.subject
		if subject == null then subject = "" # Default
		lines.add "Subject: {subject}"

		# Headers body
		var headers_body = self.headers_body
		if not headers_body.is_empty then
			for k, v in headers_body do lines.add "{k}{v}"
		end

		# Body
		var body = self.body
		if body == null then body = "" # Default

		lines.add ""
		lines.add body
		lines.add ""

		err = self.curl.native.register_callback_read(self)
		if not err.is_ok then return answer_failure(err.to_i, err.to_s)

		var content = lines.join("\n")
		err = self.curl.native.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 null
	end
end

# Callbacks Interface, allow you to manage in your way the different streams
interface CurlCallbacks
	super NativeCurlCallbacks
end

# Abstract Curl request response
abstract class CurlResponse
end

# Failed Response Class returned when errors during configuration are raised
class CurlResponseFailed
	super CurlResponse

	# Curl error code
	var error_code: Int

	# Curl error message
	var error_msg: String

	redef fun to_s do return "{error_msg} ({error_code})"
end

# Success Abstract Response Success Class
abstract class CurlResponseSuccessIntern
	super CurlCallbacks
	super CurlResponse

	var headers = new HashMap[String, String]

	# Receive headers from request due to headers callback registering
	redef fun header_callback(line)
	do
		var splitted = line.split_with(':')
		if splitted.length > 1 then
			var key = splitted.shift
			self.headers[key] = splitted.to_s
		end
	end
end

# Success Response Class of a basic response
class CurlResponseSuccess
	super CurlResponseSuccessIntern

	# Server HTTP response code
	var status_code = 0

	# Response body as a `String`
	var body_str = ""

	# Accept part of the response body
	redef fun body_callback(line) do self.body_str += line
end

# Success Response Class of a downloaded File
class CurlFileResponseSuccess
	super CurlResponseSuccessIntern

	# Server HTTP response code
	var status_code = 0

	var speed_download = 0.0
	var size_download = 0.0
	var total_time = 0.0

	private var file: nullable FileWriter = null

	# Receive bytes stream from request due to stream callback registering
	redef fun stream_callback(buffer)
	do
		file.write buffer
	end
end

# Pseudo map associating `String` to `String` for HTTP exchanges
#
# This structure differs from `Map` as each key can have multiple associations
# and the order of insertion is important to some services.
class HeaderMap
	private var array = new Array[Couple[String, String]]

	# Add a `value` associated to `key`
	fun []=(key, value: String)
	do
		array.add new Couple[String, String](key, value)
	end

	# Get a list of the keys associated to `key`
	fun [](k: String): Array[String]
	do
		var res = new Array[String]
		for c in array do if c.first == k then res.add c.second
		return res
	end

	# Iterate over all the associations in `self`
	fun iterator: MapIterator[String, String] do return new HeaderMapIterator(self)

	# Get `self` as a single string for HTTP POST
	#
	# Require: `curl.is_ok`
	private fun to_url_encoded(curl: Curl): String
	do
		assert curl.is_ok

		var lines = new Array[String]
		for k, v in self do
			if k.length == 0 then continue

			k = curl.native.escape(k)
			v = curl.native.escape(v)
			lines.add "{k}={v}"
		end
		return lines.join("&")
	end

	# Concatenate couple of 'key value' separated by 'sep' in Array
	fun join_pairs(sep: String): Array[String]
	do
		var col = new Array[String]
		for k, v in self do col.add("{k}{sep}{v}")
		return col
	end

	# Number of values in `self`
	fun length: Int do return array.length

	# Is this map empty?
	fun is_empty: Bool do return array.is_empty
end

private class HeaderMapIterator
	super MapIterator[String, String]

	var map: HeaderMap
	var iterator: Iterator[Couple[String, String]] = map.array.iterator is lazy

	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
lib/curl/curl.nit:17,1--668,3