diff options
author | Nguyễn Gia Phong <mcsinyx@disroot.org> | 2023-02-22 18:36:48 +0900 |
---|---|---|
committer | Nguyễn Gia Phong <mcsinyx@disroot.org> | 2023-02-22 18:37:33 +0900 |
commit | 4fcf1161320b28509ed7c517d38c93f26157b6af (patch) | |
tree | a3a3de16138dba07670cbf6ad3f49b4639085956 | |
parent | a6e94c3e9250ad41ddd14a15fe256570a66ff4ac (diff) | |
download | hybring-4fcf1161320b28509ed7c517d38c93f26157b6af.tar.gz |
Implement registation form
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | hybring.cr | 86 | ||||
-rw-r--r-- | style.css | 13 | ||||
-rw-r--r-- | xhtml.cr | 154 |
4 files changed, 250 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86df8d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.atom +*.db +*.xhtml diff --git a/hybring.cr b/hybring.cr index 220559f..1c5015e 100644 --- a/hybring.cr +++ b/hybring.cr @@ -1,18 +1,30 @@ -# Hybrid web ring +# Hybrid web ring server # Copyright (C) 2023 Nguyễn Gia Phong # -# This program is free software: you can redistribute it and/or modify +# This file if part of hybring. +# +# Hybring is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# This program is distributed in the hope that it will be useful, +# Hybring is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. +# along with hybring. If not, see <https://www.gnu.org/licenses/>. + +require "http/server" +require "uri" + +require "./xhtml" + +MAX_CONTENT_LENGTH = 4096 +OPENNIC_TLD = Set{".bbs", ".chan", ".cyb", ".dyn", ".epic", + ".geek", ".gopher", ".indy", ".libre", ".neo", + ".null", ".o", ".oss", ".oz", ".parody", ".pirate"} @[Link("sqlite3")] lib SQLite @@ -21,14 +33,76 @@ lib SQLite 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*, - query_tail : LibC::Char*) + query_tail : LibC::Char*) : LibC::Int + fun step = sqlite3_step(stmt : Statement) : Int32 + fun column_text = sqlite3_column_text(stmt : Statement, + col : LibC::Int) : LibC::Char* + fun finalize = sqlite3_finalize(stmt : Statement) : LibC::Int fun close = sqlite3_close(db : Database) : LibC::Int end -DB_INIT = "CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY);"; +DB_INIT = "CREATE TABLE member ( + id INTEGER PRIMARY KEY, + nick TEXT NOT NULL UNIQUE, + opennic TEXT NOT NULL UNIQUE, + icann TEXT NOT NULL UNIQUE, +);"; SQLite.open "foo.db", out db begin ensure SQLite.close db end + +def http_error(context, status, message = nil) + context.response.respond_with_status status, message +end + +File.write "index.xhtml", page +server = HTTP::Server.new do |context| + next http_error context, 405 if context.request.method != "POST" + content_length = context.request.content_length + 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 + URI::Params.parse body do |key, value| + params[key] = value + case key + when "nick" + when "opennic" + uri = URI.parse value + next errors["opennic"] = "Must be absolute URL" unless uri.absolute? + if uri.scheme != "http" && uri.scheme != "https" + next errors["opennic"] = "Must be HTTP/S" + end + host = uri.host + unless OPENNIC_TLD.includes? Path[host].extension + next errors["opennic"] = "Must be under OpenNIC domain" + end if host + when "icann" + uri = URI.parse value + next errors["icann"] = "Must be absolute URL" unless uri.absolute? + next errors["icann"] = "Must be HTTPS" unless uri.scheme == "https" + host = uri.host # impractical to check for ICANN TLD + if OPENNIC_TLD.includes? Path[host].extension + next errors["icann"] = "Must not be under OpenNIC domain" + end if host + else + break invalid_param = true + end + end + next http_error context, 400, "Invalid Parameter" if invalid_param + + context.response.status_code = 400 unless errors.empty? + context.response.content_type = "application/xhtml+xml" + context.response.print page errors, params + # TODO: schedule dynamic check +end + +address = server.bind_tcp 8080 +puts "Listening on http://#{address}" +server.listen diff --git a/style.css b/style.css new file mode 100644 index 0000000..395c35d --- /dev/null +++ b/style.css @@ -0,0 +1,13 @@ +html { + margin: auto; + max-width: 72ch; +} +h1, h2, h3, h4, h5, h6 { margin-bottom: 1ex } +form { + display: grid; + grid-template-columns: max-content 1fr; +} +form label { margin-right: 1ch } +form label:after { content: ":" } +form input { margin-bottom: 1ex } +.error { color: red } diff --git a/xhtml.cr b/xhtml.cr new file mode 100644 index 0000000..2dc8aea --- /dev/null +++ b/xhtml.cr @@ -0,0 +1,154 @@ +# XHTML generation +# Copyright (C) 2023 Nguyễn Gia Phong +# +# This file if part of hybring. +# +# Hybring is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Hybring is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# 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 "xml" + +def criteria(xml) + xml.element "h2" do xml.text "criteria" end + xml.element "p" do + xml.text "We accept pretty much any site" + xml.text " served under an OpenNIC domain" + xml.text " meeting the following requirements." + end + xml.element "ul" do + xml.element "li" do xml.text "Family-friendly content" end + xml.element "li" do xml.text "No violence incitation or doxing" end + xml.element "li" do xml.text "No user tracking" end + xml.element "li" do + xml.text "ICANN counterpart accessible via HTTPS" + end + xml.element "li" do + xml.text "Both linking to neighboring members" + end + end + xml.element "p" do + xml.text "Note that the two sites needn't be the same," + xml.text " but represent the same entity (person or organization)." + xml.text " The clear net site is for serving visitors" + xml.text " without OpenNIC access or preferring secure connections." + end +end + +def input(xml, name, label, hint, error, value) + if error + xml.element "span" + xml.element "span", class: "error" do xml.text error end + end + xml.element "label", for: name do xml.text label end + xml.element "input", name: name, placeholder: hint, value: value, + required: "required" +end + +def form(xml, errors = {} of String => String, + params = {} of String => String) + # FIXME: get URL from configuration + xml.element "form", action: "http://127.0.0.1:8080", method: "POST" do + input xml, "nick", "Nickname", "digits and ASCII lowercase letters", + errors.fetch("nick", nil), params.fetch("nick", nil) + input xml, "opennic", "OpenNIC URL", "e.g. http://example.null", + 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 "input", type: "submit", value: "Let me in!" + end +end + +def member(xml, nick, opennic, icann, feed? = false) + xml.element "p" do + xml.text "The following membership is pending for approval." + xml.text " Please subscribe to the " + # FIXME: get base URL from configuration + xml.element "a", href: "http://127.0.0.1:42069/#{nick}.atom" do + xml.text "web feed" + end + xml.text " for further instructions." + end if feed? + xml.element "h3" do xml.text nick end + xml.text "OpenNIC: " + xml.element "a", href: opennic do xml.text opennic end + xml.element "br" + xml.text "ICANN: " + xml.element "a", href: icann do xml.text icann end + xml.element "br" +end + +def page(errors = {} of String => String, + params = {} of String => String) : String + XML.build encoding: "UTF-8", indent: " " do |xml| + xml.element "html", xmlns: "http://www.w3.org/1999/xhtml", lang: "en" do + xml.element "head" do + xml.element "meta", name: "viewport", + content: "width=device-width,initial-scale=1.0" + xml.element "link", rel: "icon", href: "data:," + xml.element "link", rel: "stylesheet", + # FIXME: get base URL from configuration + href: "http://127.0.0.1:42069/style.css" + xml.element "title" do xml.text "le cercle libre" end + end + xml.element "body" do + xml.element "h1" do xml.text "le cercle libre" end + xml.element "p" do + xml.text "The Free Circle is a web ring for sites" + xml.text " with an OpenNIC domain." + end + criteria xml + + xml.element "h2" do xml.text "joining" end + # FIXME: query database for this + left = right = "http://cercle.libre" + if params.empty? # static page + xml.element "p" do + xml.text "First, add " + xml.element "a", + href: "https://html.spec.whatwg.org/#the-a-element" do + xml.text "anchors" + end + xml.text " refering to the following neighbors to your sites" + xml.text " on both OpenNIC and ICANN domains." + xml.text " Your neighbors will change as people come and go," + xml.text " so you will be given an Atom feed with instructions" + xml.text " to keep the hyperlinks up to date." + end + xml.element "ul" do + xml.element "li" do + xml.text "Left: " + xml.element "a", href: left do xml.text left end + end + xml.element "li" do + xml.text "Right: " + xml.element "a", href: right do xml.text right end + end + end + + xml.element "p" do + xml.text "Then, please fill out the form below." + end + form xml + xml.element "h2" do xml.text "members" end + # FIXME: query database for this + member xml, "self", "http://cercle.libre", "https://khoanh.loang.net" + elsif errors.empty? + member xml, params["nick"], params["opennic"], params["icann"], true + else + form xml, errors, params + end + end + end + end +end |