2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
9 local b64
= require
"util.encodings".base64
.encode
;
10 local url
= require
"socket.url"
11 local httpstream_new
= require
"net.http.parser".new
;
12 local util_http
= require
"util.http";
13 local events
= require
"util.events";
14 local verify_identity
= require
"util.x509".verify_identity
;
16 local basic_resolver
= require
"net.resolvers.basic";
17 local connect
= require
"net.connect".connect
;
19 local ssl_available
= pcall(require
, "ssl");
21 local t_insert
, t_concat
= table.insert
, table.concat
;
23 local tonumber, tostring, traceback
=
24 tonumber, tostring, debug
.traceback
;
25 local xpcall
= require
"util.xpcall".xpcall
;
28 local log = require
"util.logger".init("http");
33 local requests
= {}; -- Open requests
35 local function make_id(req
) return (tostring(req
):match("%x+$")); end
37 local listener
= { default_port
= 80, default_mode
= "*a" };
39 -- Request-related helper functions
40 local function handleerr(err
) log("error", "Traceback[http]: %s", traceback(tostring(err
), 2)); return err
; end
41 local function log_if_failed(req
, ret
, ...)
43 log("error", "Request '%s': error in callback: %s", req
.id
, (...));
44 if not req
.suppress_errors
then
51 local function destroy_request(request
)
52 local conn
= request
.conn
;
59 local function request_reader(request
, data
, err
)
60 if not request
.parser
then
61 local function error_cb(reason
)
62 if request
.callback
then
63 request
.callback(reason
or "connection-closed", 0, request
);
64 request
.callback
= nil;
66 destroy_request(request
);
74 local function success_cb(r
)
75 if request
.callback
then
76 request
.callback(r
.body
, r
.code
, r
, request
);
77 request
.callback
= nil;
79 destroy_request(request
);
81 local function options_cb()
84 request
.parser
= httpstream_new(success_cb
, error_cb
, "client", options_cb
);
86 request
.parser
:feed(data
);
89 -- Connection listener callbacks
90 function listener
.onconnect(conn
)
91 local req
= requests
[conn
];
93 -- Initialize request object
94 req
.write = function (...) return req
.conn
:write(...); end
95 local callback
= req
.callback
;
96 req
.callback
= function (content
, code
, response
, request
)
98 local event
= { http
= req
.http
, url
= req
.url
, request
= req
, response
= response
, content
= content
, code
= code
, callback
= req
.callback
};
99 req
.http
.events
.fire_event("response", event
);
100 content
, code
, response
= event
.content
, event
.code
, event
.response
;
103 log("debug", "Request '%s': Calling callback, status %s", req
.id
, code
or "---");
104 return log_if_failed(req
.id
, xpcall(callback
, handleerr
, content
, code
, response
, request
));
106 req
.reader
= request_reader
;
107 req
.state
= "status";
109 requests
[req
.conn
] = req
;
111 -- Validate certificate
112 if not req
.insecure
and conn
:ssl() then
113 local sock
= conn
:socket();
114 local chain_valid
= sock
.getpeerverification
and sock
:getpeerverification();
115 if not chain_valid
then
116 req
.callback("certificate-chain-invalid", 0, req
);
121 local cert
= sock
.getpeercertificate
and sock
:getpeercertificate();
122 if not cert
or not verify_identity(req
.host
, false, cert
) then
123 req
.callback("certificate-verify-failed", 0, req
);
131 local request_line
= { req
.method
or "GET", " ", req
.path
, " HTTP/1.1\r\n" };
133 t_insert(request_line
, 4, "?"..req
.query
);
136 conn
:write(t_concat(request_line
));
137 local t
= { [2] = ": ", [4] = "\r\n" };
138 for k
, v
in pairs(req
.headers
) do
140 conn
:write(t_concat(t
));
145 conn
:write(req
.body
);
149 function listener
.onincoming(conn
, data
)
150 local request
= requests
[conn
];
153 log("warn", "Received response from connection %s with no request attached!", conn
);
157 if data
and request
.reader
then
158 request
:reader(data
);
162 function listener
.ondisconnect(conn
, err
)
163 local request
= requests
[conn
];
164 if request
and request
.conn
then
165 request
:reader(nil, err
or "closed");
167 requests
[conn
] = nil;
170 function listener
.onattach(conn
, req
)
171 requests
[conn
] = req
;
175 function listener
.ondetach(conn
)
176 requests
[conn
] = nil;
179 function listener
.onfail(req
, reason
)
180 req
.http
.events
.fire_event("request-connection-error", { http
= req
.http
, request
= req
, url
= req
.url
, err
= reason
});
181 req
.callback(reason
or "connection failed", 0, req
);
184 local function request(self
, u
, ex
, callback
)
185 local req
= url
.parse(u
);
189 if not (req
and req
.host
) then
190 callback("invalid-url", 0, req
);
191 return nil, "invalid-url";
198 req
.id
= ex
and ex
.id
or make_id(req
);
201 local event
= { http
= self
, url
= u
, request
= req
, options
= ex
, callback
= callback
};
202 local ret
= self
.events
.fire_event("pre-request", event
);
206 req
, u
, ex
, req
.callback
= event
.request
, event
.url
, event
.options
, event
.callback
;
209 local method
, headers
, body
;
211 local host
, port
= req
.host
, req
.port
;
212 local host_header
= host
;
213 if (port
== "80" and req
.scheme
== "http")
214 or (port
== "443" and req
.scheme
== "https") then
217 host_header
= host_header
..":"..port
;
221 ["Host"] = host_header
;
222 ["User-Agent"] = "Prosody XMPP Server";
226 headers
["Authorization"] = "Basic "..b64(req
.userinfo
);
230 req
.onlystatus
= ex
.onlystatus
;
234 headers
["Content-Length"] = tostring(#body
);
235 headers
["Content-Type"] = "application/x-www-form-urlencoded";
237 if ex
.method
then method
= ex
.method
; end
239 for k
, v
in pairs(ex
.headers
) do
243 req
.insecure
= ex
.insecure
;
244 req
.suppress_errors
= ex
.suppress_errors
;
247 log("debug", "Making %s %s request '%s' to %s", req
.scheme
:upper(), method
or "GET", req
.id
, (ex
and ex
.suppress_url
and host_header
) or u
);
249 -- Attach to request object
250 req
.method
, req
.headers
, req
.body
= method
, headers
, body
;
252 local using_https
= req
.scheme
== "https";
253 if using_https
and not ssl_available
then
254 error("SSL not available, unable to contact https URL");
256 local port_number
= port
and tonumber(port
) or (using_https
and 443 or 80);
258 local sslctx
= false;
260 sslctx
= ex
and ex
.sslctx
or self
.options
and self
.options
.sslctx
;
263 local http_service
= basic_resolver
.new(host
, port_number
, "tcp", { servername
= req
.host
});
264 connect(http_service
, listener
, { sslctx
= sslctx
}, req
);
266 self
.events
.fire_event("request", { http
= self
, request
= req
, url
= u
});
270 local function new(options
)
274 new
= options
and function (new_options
)
275 local final_options
= {};
276 for k
, v
in pairs(options
) do final_options
[k
] = v
; end
278 for k
, v
in pairs(new_options
) do final_options
[k
] = v
; end
280 return new(final_options
);
282 events
= events
.new();
287 local default_http
= new({
288 sslctx
= { mode
= "client", protocol
= "sslv23", options
= { "no_sslv2", "no_sslv3" } };
289 suppress_errors
= true;
293 request
= function (u
, ex
, callback
)
294 return default_http
:request(u
, ex
, callback
);
296 default
= default_http
;
298 events
= default_http
.events
;
300 urlencode
= util_http
.urlencode
;
301 urldecode
= util_http
.urldecode
;
302 formencode
= util_http
.formencode
;
303 formdecode
= util_http
.formdecode
;