diff options
-rw-r--r-- | core/sessionmanager.lua | 2 | ||||
-rw-r--r-- | net/http/server.lua | 29 | ||||
-rw-r--r-- | plugins/mod_http.lua | 8 | ||||
-rw-r--r-- | plugins/mod_s2s/mod_s2s.lua | 6 | ||||
-rw-r--r-- | tests/test.lua | 1 | ||||
-rw-r--r-- | tests/test_utf8.lua | 19 | ||||
-rw-r--r-- | tests/utf8_sequences.txt | 52 | ||||
-rw-r--r-- | util-src/encodings.c | 117 |
8 files changed, 207 insertions, 27 deletions
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 476de931..8767e869 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -114,7 +114,7 @@ end -- returns nil, err_type, err, err_message on failure function bind_resource(session, resource) if not session.username then return nil, "auth", "not-authorized", "Cannot bind resource before authentication"; end - if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end + if session.resource then return nil, "cancel", "not-allowed", "Cannot bind multiple resources on a single connection"; end -- We don't support binding multiple resources local event_payload = { session = session, resource = resource }; diff --git a/net/http/server.lua b/net/http/server.lua index 581630d0..f091595c 100644 --- a/net/http/server.lua +++ b/net/http/server.lua @@ -189,7 +189,6 @@ function handle_request(conn, request, finish_cb) persistent = persistent; conn = conn; send = _M.send_response; - done = _M.finish_response; finish_cb = finish_cb; }; conn._http_open_response = response; @@ -209,7 +208,7 @@ function handle_request(conn, request, finish_cb) err_code, err = 400, "Missing or invalid 'Host' header"; end end - + if err then response.status_code = err_code; response:send(events.fire_event("http-error", { code = err_code, message = err })); @@ -218,7 +217,7 @@ function handle_request(conn, request, finish_cb) local event = request.method.." "..host..request.path:match("[^?]*"); local payload = { request = request, response = response }; - log("debug", event); + log("debug", "Firing event: %s", event); local result = events.fire_event(event, payload); if result ~= nil then if result ~= true then @@ -251,30 +250,24 @@ function handle_request(conn, request, finish_cb) response.status_code = 404; response:send(events.fire_event("http-error", { code = 404 })); end -local function prepare_header(response) +function _M.send_response(response, body) + if response.finished then return; end + response.finished = true; + response.conn._http_open_response = nil; + local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); local headers = response.headers; + body = body or response.body or ""; + headers.content_length = #body; + local output = { status_line }; for k,v in pairs(headers) do t_insert(output, headerfix[k]..v); end t_insert(output, "\r\n\r\n"); - return output; -end -_M.prepare_header = prepare_header; -function _M.send_response(response, body) - if response.finished then return; end - body = body or response.body or ""; - response.headers.content_length = #body; - local output = prepare_header(response); t_insert(output, body); + response.conn:write(t_concat(output)); - response:done(); -end -function _M.finish_response(response) - if response.finished then return; end - response.finished = true; - response.conn._http_open_response = nil; if response.on_destroy then response:on_destroy(); response.on_destroy = nil; diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua index 8bda1cac..086887fb 100644 --- a/plugins/mod_http.lua +++ b/plugins/mod_http.lua @@ -74,6 +74,8 @@ function moduleapi.http_url(module, app_name, default_path) return url_build(url); end end + module:log("warn", "No http ports enabled, can't generate an external URL"); + return "http://disabled.invalid/"; end function module.add_host(module) @@ -118,6 +120,12 @@ function module.add_host(module) module:log("error", "Invalid route in %s, %q. See http://prosody.im/doc/developers/http#routes", app_name, key); end end + local services = portmanager.get_active_services(); + if services:get("https") or services:get("http") then + module:log("debug", "Serving '%s' at %s", app_name, module:http_url(app_name, app_path)); + else + module:log("warn", "Not listening on any ports, '%s' will be unreachable", app_name); + end end local function http_app_removed(event) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index f9165f20..a58c6421 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -499,6 +499,12 @@ function session_stream_attrs(session, from, to, attr) if not from or (hosts[from] and hosts[from].modules.dialback) then attr["xmlns:db"] = 'jabber:server:dialback'; end + if not from then + attr.from = ''; + end + if not to then + attr.to = ''; + end end -- Session initialization logic shared by incoming and outgoing diff --git a/tests/test.lua b/tests/test.lua index f7475a80..78f2b234 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -22,6 +22,7 @@ function run_all_tests() dotest "util.sasl.scram" dosingletest("test_sasl.lua", "latin1toutf8"); + dosingletest("test_utf8.lua", "valid"); end local verbosity = tonumber(arg[1]) or 2; diff --git a/tests/test_utf8.lua b/tests/test_utf8.lua new file mode 100644 index 00000000..481eff5d --- /dev/null +++ b/tests/test_utf8.lua @@ -0,0 +1,19 @@ +package.cpath = "../?.so" +package.path = "../?.lua"; + +function valid() + local encodings = require "util.encodings"; + local utf8 = assert(encodings.utf8, "no encodings.utf8 module"); + + for line in io.lines("utf8_sequences.txt") do + local data = line:match(":%s*([^#]+)"):gsub("%s+", ""):gsub("..", function (c) return string.char(tonumber(c, 16)); end) + local expect = line:match("(%S+):"); + if expect ~= "pass" and expect ~= "fail" then + error("unknown expectation: "..line:match("^[^:]+")); + end + local prefix, style = " ", valid_style; + local valid = utf8.valid(data); + assert_equal(valid, utf8.valid(data.." ")); + assert_equal(valid, expect == "pass", line); + end +end diff --git a/tests/utf8_sequences.txt b/tests/utf8_sequences.txt new file mode 100644 index 00000000..1b967b2e --- /dev/null +++ b/tests/utf8_sequences.txt @@ -0,0 +1,52 @@ +Should pass: 41 42 43 # Simple ASCII - abc +Should pass: 41 42 c3 87 # "ABÇ" +Should pass: 41 42 e1 b8 88 # "ABḈ" +Should pass: 41 42 f0 9d 9c 8d # "AB𝜍" +Should pass: F4 8F BF BF # Last valid sequence (U+10FFFF) +Should fail: F4 90 80 80 # First invalid sequence (U+110000) +Should fail: 80 81 82 83 # Invalid sequence (invalid start byte) +Should fail: C2 C3 # Invalid sequence (invalid continuation byte) +Should fail: C0 43 # Overlong sequence +Should fail: F5 80 80 80 # U+140000 (out of range) +Should fail: ED A0 80 # U+D800 (forbidden by RFC 3629) +Should fail: ED BF BF # U+DFFF (forbidden by RFC 3629) +Should pass: ED 9F BF # U+D7FF (U+D800 minus 1: allowed) +Should pass: EE 80 80 # U+E000 (U+D7FF plus 1: allowed) +Should fail: C0 # Invalid start byte +Should fail: C1 # Invalid start byte +Should fail: C2 # Incomplete sequence +Should fail: F8 88 80 80 80 # 6-byte sequence +Should pass: 7F # Last valid 1-byte sequence (U+00007F) +Should pass: DF BF # Last valid 2-byte sequence (U+0007FF) +Should pass: EF BF BF # Last valid 3-byte sequence (U+00FFFF) +Should pass: 00 # First valid 1-byte sequence (U+000000) +Should pass: C2 80 # First valid 2-byte sequence (U+000080) +Should pass: E0 A0 80 # First valid 3-byte sequence (U+000800) +Should pass: F0 90 80 80 # First valid 4-byte sequence (U+000800) +Should fail: F8 88 80 80 80 # First 5-byte sequence - invalid per RFC 3629 +Should fail: FC 84 80 80 80 80 # First 6-byte sequence - invalid per RFC 3629 +Should pass: EF BF BD # U+00FFFD (replacement character) +Should fail: 80 # First continuation byte +Should fail: BF # Last continuation byte +Should fail: 80 BF # 2 continuation bytes +Should fail: 80 BF 80 # 3 continuation bytes +Should fail: 80 BF 80 BF # 4 continuation bytes +Should fail: 80 BF 80 BF 80 # 5 continuation bytes +Should fail: 80 BF 80 BF 80 BF # 6 continuation bytes +Should fail: 80 BF 80 BF 80 BF 80 # 7 continuation bytes +Should fail: FE # Impossible byte +Should fail: FF # Impossible byte +Should fail: FE FE FF FF # Impossible bytes +Should fail: C0 AF # Overlong "/" +Should fail: E0 80 AF # Overlong "/" +Should fail: F0 80 80 AF # Overlong "/" +Should fail: F8 80 80 80 AF # Overlong "/" +Should fail: FC 80 80 80 80 AF # Overlong "/" +Should fail: C0 80 AF # Overlong "/" (invalid) +Should fail: C1 BF # Overlong +Should fail: E0 9F BF # Overlong +Should fail: F0 8F BF BF # Overlong +Should fail: F8 87 BF BF BF # Overlong +Should fail: FC 83 BF BF BF BF # Overlong +Should pass: EF BF BE # U+FFFE (invalid unicode, valid UTF-8) +Should pass: EF BF BF # U+FFFF (invalid unicode, valid UTF-8) diff --git a/util-src/encodings.c b/util-src/encodings.c index 2d5d49d4..0841e29f 100644 --- a/util-src/encodings.c +++ b/util-src/encodings.c @@ -1,6 +1,7 @@ /* Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 1994-2015 Lua.org, PUC-Rio. -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. @@ -120,6 +121,88 @@ static const luaL_Reg Reg_base64[] = { NULL, NULL } }; +/******************* UTF-8 ********************/ + +/* + * Adapted from Lua 5.3 + * Needed because libidn does not validate that input is valid UTF-8 + */ + +#define MAXUNICODE 0x10FFFF + +/* + * Decode one UTF-8 sequence, returning NULL if byte sequence is invalid. + */ +static const char *utf8_decode (const char *o, int *val) { + static unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF}; + const unsigned char *s = (const unsigned char *)o; + unsigned int c = s[0]; + unsigned int res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + while (c & 0x40) { /* still have continuation bytes? */ + int cc = s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + c <<= 1; /* to test next bit */ + } + res |= ((c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 3 || res > MAXUNICODE || res <= limits[count] || (0xd800 <= res && res <= 0xdfff) ) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (val) *val = res; + return (const char *)s + 1; /* +1 to include first byte */ +} + +/* + * Check that a string is valid UTF-8 + * Returns NULL if not + */ +const char* check_utf8 (lua_State *L, int idx, size_t *l) { + size_t pos, len; + const char *s = luaL_checklstring(L, 1, &len); + pos = 0; + while (pos <= len) { + const char *s1 = utf8_decode(s + pos, NULL); + if (s1 == NULL) { /* conversion error? */ + return NULL; + } + pos = s1 - s; + } + if(l != NULL) { + *l = len; + } + return s; +} + +static int Lutf8_valid(lua_State *L) { + lua_pushboolean(L, check_utf8(L, 1, NULL) != NULL); + return 1; +} + +static int Lutf8_length(lua_State *L) { + size_t len; + if(!check_utf8(L, 1, &len)) { + lua_pushnil(L); + lua_pushliteral(L, "invalid utf8"); + return 2; + } + lua_pushinteger(L, len); + return 1; +} + +static const luaL_Reg Reg_utf8[] = +{ + { "valid", Lutf8_valid }, + { "length", Lutf8_length }, + { NULL, NULL } +}; + + /***************** STRINGPREP *****************/ #ifdef USE_STRINGPREP_ICU @@ -216,8 +299,8 @@ static int stringprep_prep(lua_State *L, const Stringprep_profile *profile) lua_pushnil(L); return 1; } - s = lua_tolstring(L, 1, &len); - if (len >= 1024) { + s = check_utf8(L, 1, &len); + if (s == NULL || len >= 1024 || len != strlen(s)) { lua_pushnil(L); return 1; /* TODO return error message */ } @@ -324,7 +407,11 @@ static int Lidna_to_unicode(lua_State *L) /** idna.to_unicode(s) */ static int Lidna_to_ascii(lua_State *L) /** idna.to_ascii(s) */ { size_t len; - const char *s = luaL_checklstring(L, 1, &len); + const char *s = check_utf8(L, 1, &len); + if (s == NULL || len != strlen(s)) { + lua_pushnil(L); + return 1; /* TODO return error message */ + } char* output = NULL; int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES); if (ret == IDNA_SUCCESS) { @@ -365,26 +452,40 @@ static const luaL_Reg Reg_idna[] = /***************** end *****************/ +static const luaL_Reg Reg[] = +{ + { NULL, NULL } +}; + LUALIB_API int luaopen_util_encodings(lua_State *L) { #ifdef USE_STRINGPREP_ICU init_icu(); #endif - lua_newtable(L); + luaL_register(L, "encodings", Reg); + lua_pushliteral(L, "base64"); lua_newtable(L); luaL_register(L, NULL, Reg_base64); - lua_setfield(L, -2, "base64"); + lua_settable(L,-3); + lua_pushliteral(L, "stringprep"); lua_newtable(L); luaL_register(L, NULL, Reg_stringprep); - lua_setfield(L, -2, "stringprep"); + lua_settable(L,-3); + lua_pushliteral(L, "idna"); lua_newtable(L); luaL_register(L, NULL, Reg_idna); - lua_setfield(L, -2, "idna"); + lua_settable(L,-3); + + lua_pushliteral(L, "utf8"); + lua_newtable(L); + luaL_register(L, NULL, Reg_utf8); + lua_settable(L, -3); + lua_pushliteral(L, "version"); /** version */ lua_pushliteral(L, "-3.14"); - lua_setfield(L, -2, "version"); + lua_settable(L,-3); return 1; } |