summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNguyễn Gia Phong <mcsinyx@disroot.org>2023-02-22 18:36:48 +0900
committerNguyễn Gia Phong <mcsinyx@disroot.org>2023-02-22 18:37:33 +0900
commit4fcf1161320b28509ed7c517d38c93f26157b6af (patch)
treea3a3de16138dba07670cbf6ad3f49b4639085956
parenta6e94c3e9250ad41ddd14a15fe256570a66ff4ac (diff)
downloadhybring-4fcf1161320b28509ed7c517d38c93f26157b6af.tar.gz
Implement registation form
-rw-r--r--.gitignore3
-rw-r--r--hybring.cr86
-rw-r--r--style.css13
-rw-r--r--xhtml.cr154
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