util.x509: Nameprep commonName once
[prosody.git] / net / http.lua
blob055fc9362cd5e32f782d91a41285abeb4b7c2271
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
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;
22 local pairs = pairs;
23 local tonumber, tostring, traceback =
24 tonumber, tostring, debug.traceback;
25 local xpcall = require "util.xpcall".xpcall;
26 local error = error
28 local log = require "util.logger".init("http");
30 local _ENV = nil;
31 -- luacheck: std none
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, ...)
42 if not ret then
43 log("error", "Request '%s': error in callback: %s", req.id, (...));
44 if not req.suppress_errors then
45 error(...);
46 end
47 end
48 return ...;
49 end
51 local function destroy_request(request)
52 local conn = request.conn;
53 if conn then
54 request.conn = nil;
55 conn:close()
56 end
57 end
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;
65 end
66 destroy_request(request);
67 end
69 if not data then
70 error_cb(err);
71 return;
72 end
74 local function success_cb(r)
75 if request.callback then
76 request.callback(r.body, r.code, r, request);
77 request.callback = nil;
78 end
79 destroy_request(request);
80 end
81 local function options_cb()
82 return request;
83 end
84 request.parser = httpstream_new(success_cb, error_cb, "client", options_cb);
85 end
86 request.parser:feed(data);
87 end
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);
117 req.callback = nil;
118 conn:close();
119 return;
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);
124 req.callback = nil;
125 conn:close();
126 return;
130 -- Send the request
131 local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" };
132 if req.query then
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
139 t[1], t[3] = k, v;
140 conn:write(t_concat(t));
142 conn:write("\r\n");
144 if req.body then
145 conn:write(req.body);
149 function listener.onincoming(conn, data)
150 local request = requests[conn];
152 if not request then
153 log("warn", "Received response from connection %s with no request attached!", conn);
154 return;
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;
172 req.conn = conn;
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);
186 req.url = u;
187 req.http = self;
189 if not (req and req.host) then
190 callback("invalid-url", 0, req);
191 return nil, "invalid-url";
194 if not req.path then
195 req.path = "/";
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);
203 if ret then
204 return ret;
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
215 port = nil;
216 elseif port then
217 host_header = host_header..":"..port;
220 headers = {
221 ["Host"] = host_header;
222 ["User-Agent"] = "Prosody XMPP Server";
225 if req.userinfo then
226 headers["Authorization"] = "Basic "..b64(req.userinfo);
229 if ex then
230 req.onlystatus = ex.onlystatus;
231 body = ex.body;
232 if body then
233 method = "POST";
234 headers["Content-Length"] = tostring(#body);
235 headers["Content-Type"] = "application/x-www-form-urlencoded";
237 if ex.method then method = ex.method; end
238 if ex.headers then
239 for k, v in pairs(ex.headers) do
240 headers[k] = v;
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;
259 if using_https then
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 });
267 return req;
270 local function new(options)
271 local http = {
272 options = options;
273 request = request;
274 new = options and function (new_options)
275 local final_options = {};
276 for k, v in pairs(options) do final_options[k] = v; end
277 if new_options then
278 for k, v in pairs(new_options) do final_options[k] = v; end
280 return new(final_options);
281 end or new;
282 events = events.new();
284 return http;
287 local default_http = new({
288 sslctx = { mode = "client", protocol = "sslv23", options = { "no_sslv2", "no_sslv3" } };
289 suppress_errors = true;
292 return {
293 request = function (u, ex, callback)
294 return default_http:request(u, ex, callback);
295 end;
296 default = default_http;
297 new = new;
298 events = default_http.events;
299 -- COMPAT
300 urlencode = util_http.urlencode;
301 urldecode = util_http.urldecode;
302 formencode = util_http.formencode;
303 formdecode = util_http.formdecode;