diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | http.cr (renamed from hybring.cr) | 34 | ||||
-rw-r--r-- | sqlite.cr | 17 | ||||
-rw-r--r-- | xhtml.cr | 14 |
4 files changed, 52 insertions, 16 deletions
diff --git a/.gitignore b/.gitignore index 66533b2..272ac67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -static/ -*.db +data/ *.ini diff --git a/hybring.cr b/http.cr index 13ba893..c1de4dc 100644 --- a/hybring.cr +++ b/http.cr @@ -1,4 +1,4 @@ -# Hybrid web ring server +# HTTP server # Copyright (C) 2023 Nguyễn Gia Phong # # This file if part of hybring. @@ -24,6 +24,7 @@ require "./sqlite" require "./xhtml" MAX_CONTENT_LENGTH = 4096 +MAX_NICK_LENGTH = 32 OPENNIC_TLD = Set{".bbs", ".chan", ".cyb", ".dyn", ".epic", ".geek", ".gopher", ".indy", ".libre", ".neo", ".null", ".o", ".oss", ".oz", ".parody", ".pirate"} @@ -37,10 +38,19 @@ if ARGV.empty? exit 1 end cfg = INI.parse File.read ARGV[0] -db = Database.new cfg["path"]["db"] +opennic_host = URI.parse(cfg["opennic"]["remote"]).host + +Dir.mkdir_p Path[cfg["general"]["db"]].parent +db = Database.new cfg["general"]["db"], + cfg["opennic"]["remote"], cfg["icann"]["remote"] members = db.members -page = Page.new cfg["url"]["static"], cfg["url"]["api"] -File.write Path[cfg["path"]["static"]] / "index.xhtml", page.build members + +opennic_page = Page.new cfg["opennic"]["local"], cfg["opennic"]["remote"], + cfg["general"]["api"] +opennic_page.write members +icann_page = Page.new cfg["icann"]["local"], cfg["icann"]["remote"], + cfg["general"]["api"] +icann_page.write members server = HTTP::Server.new do |context| # Manually crafted request @@ -49,14 +59,17 @@ server = HTTP::Server.new do |context| next http_error context, 411 unless content_length next http_error context, 413 if content_length > MAX_CONTENT_LENGTH - body = context.request.body.try &.gets_to_end || "" errors = {} of String => String params = {} of String => String invalid_param = false + body = context.request.body.try &.gets_to_end || "" URI::Params.parse body do |key, value| params[key] = value case key when "nick" + if value.size > MAX_NICK_LENGTH + next errors["nick"] = "Must be within ${MAX_NICK_LENGTH} characters" + end if /^[0-9a-z]+$/ !~ value next errors["nick"] = "Must be ASCII lowercase alphanumeric" end @@ -78,6 +91,7 @@ server = HTTP::Server.new do |context| if OPENNIC_TLD.includes? Path[host].extension next errors["icann"] = "Must not be under OpenNIC domain" end if host + when "host" else break invalid_param = true end @@ -85,7 +99,7 @@ server = HTTP::Server.new do |context| # Manually crafted request next http_error context, 400, "Invalid Parameter" if invalid_param - next http_error context, 400, "Missing Parameter" unless params.size == 3 + next http_error context, 400, "Missing Parameter" unless params.size == 4 members.each do |nick, opennic, icann| errors["nick"] = "Must be unique" if nick == params["nick"] @@ -99,11 +113,15 @@ server = HTTP::Server.new do |context| context.response.status_code = 400 unless errors.empty? end context.response.content_type = "application/xhtml+xml" - context.response.print page.build members, errors, params + if params["host"] == opennic_host + context.response.print opennic_page.build members, errors, params + else + context.response.print icann_page.build members, errors, params + end # TODO: schedule dynamic check end # TODO: support Unix socket -address = server.bind_tcp cfg["listen"]["port"].to_i +address = server.bind_tcp cfg["general"]["port"].to_i puts "Listening on http://#{address}" server.listen diff --git a/sqlite.cr b/sqlite.cr index 4cca647..5dfbe4d 100644 --- a/sqlite.cr +++ b/sqlite.cr @@ -26,6 +26,9 @@ lib SQLite type Statement = Void* fun errstr = sqlite3_errstr(rc : LibC::Int) : LibC::Char* + fun mprintf = sqlite3_mprintf(format : LibC::Char*, ...) : LibC::Char* + fun free = sqlite3_free(format : Void*, ...) + fun open = sqlite3_open(filename : LibC::Char*, db : Database*) : LibC::Int fun prepare = sqlite3_prepare(db : Database, query : LibC::Char*, length : LibC::Int, stmt : Statement*, @@ -45,7 +48,9 @@ class Database id INTEGER PRIMARY KEY, nick TEXT NOT NULL UNIQUE, opennic TEXT NOT NULL UNIQUE, - icann TEXT NOT NULL UNIQUE)"; + icann TEXT NOT NULL UNIQUE)" + INSERT_MEMBER = "INSERT INTO member (nick, opennic, icann) + VALUES (%Q, %Q, %Q)" class Statement def initialize(db, query) @@ -92,7 +97,7 @@ class Database end end - def initialize(path : String) + def initialize(path : String, opennic, icann) Database.check SQLite.open path, out @ref self.exec "PRAGMA user_version" do |row| version = row[0].int @@ -101,6 +106,8 @@ class Database if version == 0 self.exec SCHEMA do end self.exec "PRAGMA user_version = #{MIGRATIONS.size}" do end + # Avoid out-of-bound when looking for neighbors. + self.exec INSERT_MEMBER, "self", opennic, icann do end end rescue ex self.finalize @@ -108,8 +115,10 @@ class Database end end - def exec(query : String) - stmt = Statement.new @ref, query + def exec(query : String, *values) + sql = SQLite.mprintf query, *values + stmt = Statement.new @ref, String.new sql + SQLite.free sql loop do rc = stmt.step case rc diff --git a/xhtml.cr b/xhtml.cr index 7e21309..4749af1 100644 --- a/xhtml.cr +++ b/xhtml.cr @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Affero General Public License # along with hybring. If not, see <https://www.gnu.org/licenses/>. +require "uri" require "xml" CSS = " @@ -35,8 +36,11 @@ CSS = " " class Page - def initialize(static_url : String, api_url : String) + def initialize(static_dir : String, static_url : String, api_url : String) + Dir.mkdir_p static_dir + @static_file = Path[static_dir] / "index.xhtml" @static_url = static_url + @static_host = URI.parse(static_url).host @api_url = api_url end @@ -87,7 +91,9 @@ class Page errors.fetch("opennic", nil), params.fetch("opennic", nil) input xml, "icann", "ICANN URL", "e.g. https://example.net", errors.fetch("icann", nil), params.fetch("icann", nil) - xml.element "span" + xml.element "span" do + xml.element "input", type: "hidden", name: "host", value: @static_host + end xml.element "input", type: "submit", value: "Let me in!" xml.element "br" end @@ -172,4 +178,8 @@ class Page end end end + + def write(members) + File.write @static_file, self.build members + end end |