2018-02-04 03:22:01 +01:00
|
|
|
// Matrix Construct
|
|
|
|
//
|
|
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice is present in all copies. The
|
|
|
|
// full license for this software is available in the LICENSE file.
|
2017-08-23 23:10:28 +02:00
|
|
|
|
|
|
|
using namespace ircd;
|
|
|
|
|
2018-06-12 08:49:13 +02:00
|
|
|
std::map<std::string, std::string, iless> files;
|
|
|
|
|
|
|
|
static string_view
|
|
|
|
content_type(const mutable_buffer &out, const string_view &filename, const string_view &content);
|
|
|
|
|
|
|
|
resource::response
|
|
|
|
get_root(client &client, const resource::request &request);
|
|
|
|
|
2019-02-25 00:12:31 +01:00
|
|
|
resource::response
|
|
|
|
non_get_root(client &client, const resource::request &request);
|
|
|
|
|
2018-06-12 08:49:13 +02:00
|
|
|
static void
|
|
|
|
init_files();
|
|
|
|
|
|
|
|
mapi::header
|
|
|
|
IRCD_MODULE
|
|
|
|
{
|
|
|
|
"Web root content resource",
|
|
|
|
init_files
|
|
|
|
};
|
|
|
|
|
|
|
|
resource
|
|
|
|
root_resource
|
|
|
|
{
|
|
|
|
"/",
|
|
|
|
{
|
|
|
|
"Webroot resource",
|
|
|
|
root_resource.DIRECTORY
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
resource::method
|
|
|
|
root_get
|
|
|
|
{
|
|
|
|
root_resource, "GET", get_root
|
|
|
|
};
|
2017-08-23 23:10:28 +02:00
|
|
|
|
2019-02-25 00:12:31 +01:00
|
|
|
resource::method
|
|
|
|
root_put
|
|
|
|
{
|
|
|
|
root_resource, "PUT", non_get_root
|
|
|
|
};
|
|
|
|
|
|
|
|
resource::method
|
|
|
|
root_post
|
|
|
|
{
|
|
|
|
root_resource, "POST", non_get_root
|
|
|
|
};
|
|
|
|
|
|
|
|
resource::method
|
|
|
|
root_delete
|
|
|
|
{
|
|
|
|
root_resource, "DELETE", non_get_root
|
|
|
|
};
|
|
|
|
|
2019-08-03 23:56:28 +02:00
|
|
|
/// LEGACY
|
2018-09-03 13:21:53 +02:00
|
|
|
conf::item<std::string>
|
|
|
|
webroot_path
|
|
|
|
{
|
|
|
|
{ "name", "ircd.webroot.path" },
|
|
|
|
{ "default", "" },
|
|
|
|
};
|
|
|
|
|
2019-06-27 08:08:11 +02:00
|
|
|
conf::item<std::string>
|
2019-08-03 23:56:28 +02:00
|
|
|
root_path
|
2019-06-27 08:08:11 +02:00
|
|
|
{
|
2019-08-03 23:56:28 +02:00
|
|
|
{ "name", "ircd.web.root.path" },
|
2019-06-27 08:08:11 +02:00
|
|
|
{ "default", string_view{webroot_path} },
|
|
|
|
};
|
|
|
|
|
2020-08-07 03:59:58 +02:00
|
|
|
conf::item<bool>
|
|
|
|
root_cache_control_immutable
|
|
|
|
{
|
|
|
|
{ "name", "ircd.web.root.cache_control.immutable" },
|
|
|
|
{ "default", true },
|
|
|
|
};
|
|
|
|
|
2017-08-23 23:10:28 +02:00
|
|
|
void
|
2018-01-11 12:24:22 +01:00
|
|
|
init_files()
|
2017-08-23 23:10:28 +02:00
|
|
|
{
|
2019-06-27 08:08:11 +02:00
|
|
|
const string_view &path
|
2018-12-09 00:52:10 +01:00
|
|
|
{
|
2019-08-03 23:56:28 +02:00
|
|
|
root_path
|
2018-12-09 00:52:10 +01:00
|
|
|
};
|
|
|
|
|
2018-09-03 13:21:53 +02:00
|
|
|
if(empty(path))
|
2019-08-04 00:08:39 +02:00
|
|
|
{
|
|
|
|
log::warning
|
|
|
|
{
|
|
|
|
"Conf item 'ircd.web.root.path' is empty; not serving static assets."
|
|
|
|
};
|
|
|
|
|
2018-09-03 13:21:53 +02:00
|
|
|
return;
|
2019-08-04 00:08:39 +02:00
|
|
|
}
|
2018-09-03 13:21:53 +02:00
|
|
|
|
2018-12-09 00:52:10 +01:00
|
|
|
if(!fs::exists(path))
|
|
|
|
{
|
|
|
|
log::error
|
|
|
|
{
|
|
|
|
"Configured ircd.webroot.path at `%s' does not exist.", path
|
|
|
|
};
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-08-04 00:45:49 +02:00
|
|
|
for(const auto &absolute : fs::ls_r(path))
|
2017-08-23 23:10:28 +02:00
|
|
|
{
|
2019-08-04 00:45:49 +02:00
|
|
|
// fs::ls_r() gives us full absolute paths on the system, but we need
|
|
|
|
// to locate resources relative to webroot. The system path below
|
|
|
|
// the configured webroot is stripped here.
|
|
|
|
auto relative
|
2019-08-04 00:08:39 +02:00
|
|
|
{
|
2019-08-04 00:45:49 +02:00
|
|
|
lstrip(absolute, path)
|
2019-08-04 00:08:39 +02:00
|
|
|
};
|
|
|
|
|
2019-08-04 00:45:49 +02:00
|
|
|
// The configured webroot is a directory string entered by the admin, it
|
|
|
|
// may or may not contain a trailing slash. This the strip above may have
|
|
|
|
// left a leading slash on this name; which is bad.
|
|
|
|
relative = lstrip(relative, '/');
|
|
|
|
|
|
|
|
// Add the mapping of the relative resource path to the absolute path
|
|
|
|
// on the system.
|
|
|
|
files.emplace(relative, absolute);
|
2017-08-23 23:10:28 +02:00
|
|
|
}
|
2019-08-04 00:08:39 +02:00
|
|
|
|
|
|
|
if(files.empty())
|
|
|
|
{
|
|
|
|
log::dwarning
|
|
|
|
{
|
|
|
|
"No files or directories found at `%s'; not serving any static assets."
|
|
|
|
};
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
log::info
|
|
|
|
{
|
|
|
|
"Web root loaded %zu file and directory resources for service under `%s'",
|
|
|
|
files.size(),
|
|
|
|
path,
|
|
|
|
};
|
2017-08-23 23:10:28 +02:00
|
|
|
}
|
|
|
|
|
2019-02-25 00:12:31 +01:00
|
|
|
/// This handler exists because the root resource on path "/" catches
|
|
|
|
/// everything rejected by all the other registered resources; after that
|
|
|
|
/// happens if the method was not GET the client always gets a 405 even if
|
|
|
|
/// the path they specified truly does not exist. This handler allows us to
|
|
|
|
/// give them a 404 first instead by checking the path's existence; then a 405
|
|
|
|
/// if it does exist and they did not use GET.
|
|
|
|
resource::response
|
|
|
|
non_get_root(client &client,
|
|
|
|
const resource::request &request)
|
|
|
|
{
|
2020-10-06 04:36:17 +02:00
|
|
|
const auto &request_head_path
|
2019-02-25 00:12:31 +01:00
|
|
|
{
|
|
|
|
!request.head.path?
|
|
|
|
"index.html":
|
|
|
|
request.head.path == "/"?
|
|
|
|
"index.html":
|
2020-10-06 04:36:17 +02:00
|
|
|
request.head.path
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto &path
|
|
|
|
{
|
|
|
|
lstrip(request_head_path, '/')
|
2019-02-25 00:12:31 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const http::code code
|
|
|
|
{
|
2020-10-06 04:36:17 +02:00
|
|
|
files.count(path)?
|
2019-02-25 00:12:31 +01:00
|
|
|
http::METHOD_NOT_ALLOWED:
|
|
|
|
http::NOT_FOUND
|
|
|
|
};
|
|
|
|
|
|
|
|
return resource::response
|
|
|
|
{
|
|
|
|
client, code
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-08-23 23:10:28 +02:00
|
|
|
resource::response
|
2018-03-22 08:45:49 +01:00
|
|
|
get_root(client &client,
|
|
|
|
const resource::request &request)
|
|
|
|
try
|
2017-08-23 23:10:28 +02:00
|
|
|
{
|
2020-10-06 04:36:17 +02:00
|
|
|
const auto &request_head_path
|
2017-10-03 13:12:54 +02:00
|
|
|
{
|
2018-03-22 08:45:49 +01:00
|
|
|
!request.head.path?
|
|
|
|
"index.html":
|
|
|
|
request.head.path == "/"?
|
|
|
|
"index.html":
|
2020-10-06 04:36:17 +02:00
|
|
|
request.head.path
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto &path
|
|
|
|
{
|
|
|
|
lstrip(request_head_path, '/')
|
2017-10-03 13:12:54 +02:00
|
|
|
};
|
|
|
|
|
2018-03-22 08:45:49 +01:00
|
|
|
auto it
|
|
|
|
{
|
2020-10-06 04:36:17 +02:00
|
|
|
files.find(path)
|
2018-03-22 08:45:49 +01:00
|
|
|
};
|
|
|
|
|
2017-08-23 23:10:28 +02:00
|
|
|
if(it == end(files))
|
2020-10-06 05:09:21 +02:00
|
|
|
return resource::response
|
|
|
|
{
|
|
|
|
client, http::NOT_FOUND
|
|
|
|
};
|
2017-08-23 23:10:28 +02:00
|
|
|
|
2018-05-30 11:26:57 +02:00
|
|
|
const auto &file_name
|
2018-04-14 09:25:09 +02:00
|
|
|
{
|
2018-05-30 11:26:57 +02:00
|
|
|
it->second
|
|
|
|
};
|
|
|
|
|
|
|
|
const fs::fd fd
|
|
|
|
{
|
|
|
|
file_name
|
2018-04-14 09:25:09 +02:00
|
|
|
};
|
2018-03-22 08:45:49 +01:00
|
|
|
|
2018-04-26 03:17:29 +02:00
|
|
|
const size_t file_size
|
2018-03-22 08:45:49 +01:00
|
|
|
{
|
2018-05-30 11:26:57 +02:00
|
|
|
size(fd)
|
|
|
|
};
|
|
|
|
|
|
|
|
const unique_buffer<mutable_buffer> buffer
|
|
|
|
{
|
|
|
|
24_KiB
|
2018-04-26 03:17:29 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
string_view chunk
|
|
|
|
{
|
2018-05-30 11:26:57 +02:00
|
|
|
fs::read(fd, buffer)
|
2018-03-22 08:45:49 +01:00
|
|
|
};
|
|
|
|
|
2020-08-07 03:59:58 +02:00
|
|
|
static const string_view &cache_control_immutable
|
|
|
|
{
|
|
|
|
"Cache-Control: public, max-age=31536000, immutable\r\n"_sv
|
|
|
|
};
|
|
|
|
|
|
|
|
// Responses from this handler are assumed to be static content by
|
|
|
|
// default. Developers or applications with mutable static content
|
|
|
|
// can disable the header at runtime with this conf item.
|
|
|
|
const string_view &addl_headers
|
|
|
|
{
|
2020-10-06 04:36:17 +02:00
|
|
|
// Don't add this header for index.html otherwise firefox makes really
|
|
|
|
// aggressive assumptions on page-load which are fantastic right until
|
|
|
|
// you upgrade Riot and then all hell breaks loose as your client
|
|
|
|
// straddles between two versions at the same time.
|
|
|
|
path == "index.html"?
|
|
|
|
string_view{}:
|
|
|
|
|
|
|
|
// Add header if conf item allows
|
2020-08-07 03:59:58 +02:00
|
|
|
root_cache_control_immutable?
|
|
|
|
cache_control_immutable:
|
2020-10-06 04:36:17 +02:00
|
|
|
|
|
|
|
// Else don't add header
|
2020-08-07 03:59:58 +02:00
|
|
|
string_view{}
|
|
|
|
};
|
|
|
|
|
2018-03-22 08:45:49 +01:00
|
|
|
char content_type_buf[64];
|
|
|
|
resource::response
|
|
|
|
{
|
|
|
|
client,
|
|
|
|
http::OK,
|
2018-04-26 03:17:29 +02:00
|
|
|
content_type(content_type_buf, file_name, chunk),
|
2020-08-07 03:59:58 +02:00
|
|
|
file_size,
|
|
|
|
addl_headers,
|
2018-03-22 08:45:49 +01:00
|
|
|
};
|
|
|
|
|
2019-12-03 22:05:32 +01:00
|
|
|
const unwind_exceptional terminate{[&client]
|
2019-03-23 02:54:33 +01:00
|
|
|
{
|
|
|
|
client.close(net::dc::RST, net::close_ignore);
|
|
|
|
}};
|
|
|
|
|
2018-04-26 03:17:29 +02:00
|
|
|
size_t written
|
|
|
|
{
|
|
|
|
client.write_all(chunk)
|
|
|
|
};
|
|
|
|
|
|
|
|
size_t offset
|
|
|
|
{
|
|
|
|
size(chunk)
|
|
|
|
};
|
2018-03-22 08:45:49 +01:00
|
|
|
|
2018-04-26 03:17:29 +02:00
|
|
|
while(offset < file_size)
|
2018-03-22 08:45:49 +01:00
|
|
|
{
|
2018-05-30 11:26:57 +02:00
|
|
|
chunk = fs::read(fd, buffer, offset);
|
2018-04-26 03:17:29 +02:00
|
|
|
assert(!empty(chunk));
|
|
|
|
written += client.write_all(chunk);
|
2018-03-22 08:45:49 +01:00
|
|
|
offset += size(chunk);
|
2018-04-26 03:17:29 +02:00
|
|
|
assert(written == offset);
|
2018-03-22 08:45:49 +01:00
|
|
|
}
|
|
|
|
|
2018-04-26 03:17:29 +02:00
|
|
|
assert(offset == file_size);
|
|
|
|
assert(written == offset);
|
2018-03-22 08:45:49 +01:00
|
|
|
return {};
|
|
|
|
}
|
2018-08-23 13:18:48 +02:00
|
|
|
catch(const fs::error &e)
|
2018-03-22 08:45:49 +01:00
|
|
|
{
|
|
|
|
throw http::error
|
|
|
|
{
|
2018-12-13 02:19:49 +01:00
|
|
|
e.code() == std::errc::no_such_file_or_directory?
|
2018-08-23 13:18:48 +02:00
|
|
|
http::NOT_FOUND:
|
|
|
|
http::INTERNAL_SERVER_ERROR,
|
|
|
|
|
|
|
|
"%s", e.what()
|
2018-03-22 08:45:49 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
string_view
|
|
|
|
content_type(const mutable_buffer &out,
|
|
|
|
const string_view &filename,
|
|
|
|
const string_view &content)
|
|
|
|
{
|
2018-02-12 23:47:03 +01:00
|
|
|
const auto extension
|
|
|
|
{
|
|
|
|
rsplit(filename, '.').second
|
|
|
|
};
|
|
|
|
|
|
|
|
string_view content_type; switch(hash(extension))
|
2017-08-23 23:10:28 +02:00
|
|
|
{
|
|
|
|
case hash("css"): content_type = "text/css; charset=utf-8"; break;
|
|
|
|
case hash("js"): content_type = "application/javascript; charset=utf-8"; break;
|
2019-06-29 09:22:35 +02:00
|
|
|
case hash("wasm"): content_type = "application/wasm"; break;
|
2017-08-23 23:10:28 +02:00
|
|
|
case hash("html"): content_type = "text/html; charset=utf-8"; break;
|
|
|
|
case hash("ico"): content_type = "image/x-icon"; break;
|
|
|
|
case hash("svg"): content_type = "image/svg+xml"; break;
|
|
|
|
case hash("png"): content_type = "image/png"; break;
|
2018-02-12 23:47:03 +01:00
|
|
|
case hash("gif"): content_type = "image/gif"; break;
|
|
|
|
case hash("jpeg"):
|
|
|
|
case hash("jpg"): content_type = "image/jpeg"; break;
|
2017-08-23 23:10:28 +02:00
|
|
|
case hash("woff2"): content_type = "application/font-woff2"; break;
|
|
|
|
case hash("woff"): content_type = "application/font-woff"; break;
|
|
|
|
case hash("eot"): content_type = "application/vnd.ms-fontobject"; break;
|
|
|
|
case hash("otf"):
|
|
|
|
case hash("ttf"): content_type = "application/font-sfnt"; break;
|
2018-02-12 23:47:03 +01:00
|
|
|
case hash("ogg"): content_type = "application/ogg"; break;
|
|
|
|
case hash("json"): content_type = "application/json; charset=utf-8"; break;
|
|
|
|
case hash("txt"): content_type = "text/plain; charset=utf-8"; break;
|
|
|
|
default:
|
|
|
|
{
|
2018-03-22 08:45:49 +01:00
|
|
|
content_type = magic::mime(out, content);
|
2018-02-12 23:47:03 +01:00
|
|
|
break;
|
|
|
|
}
|
2017-08-23 23:10:28 +02:00
|
|
|
}
|
|
|
|
|
2018-03-22 08:45:49 +01:00
|
|
|
return content_type;
|
2017-08-23 23:10:28 +02:00
|
|
|
}
|