From 3967e5fdb791187c0033be1faa4ac44983e72990 Mon Sep 17 00:00:00 2001
From: brent saner
Date: Thu, 12 Dec 2024 03:47:30 -0500
Subject: [PATCH] v0.0.1
Initial public release
---
server/funcs_server.go | 250 +---------------------------------
server/tpl/meta.info.html.tpl | 62 ++++++---
server/tpl/meta.top.html.tpl | 64 ++++++---
server/tpl/usage.html.tpl | 25 ++--
4 files changed, 108 insertions(+), 293 deletions(-)
diff --git a/server/funcs_server.go b/server/funcs_server.go
index 9213d97..af1c251 100644
--- a/server/funcs_server.go
+++ b/server/funcs_server.go
@@ -1,12 +1,10 @@
package server
import (
- `crypto/tls`
`encoding/json`
`encoding/xml`
"errors"
"fmt"
- `mime/multipart`
"net"
"net/http"
`net/http/fcgi`
@@ -267,246 +265,6 @@ func (s *Server) explicit404(resp http.ResponseWriter, req *http.Request) {
func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
- var vals url.Values
- var uaVals []string
- var doInclude bool
- var doIndent bool
- var err error
- var ok bool
- var b []byte
- var remAddrPort string
- var okMedia []string
- var nAP netip.AddrPort
- var parsedFmts []*parsedMIME
- var renderPage *Page = new(Page)
- var format string = mediaJSON
- var indent string = " "
- var client *R00tInfo = new(R00tInfo)
-
- renderPage.RawIndent = " "
- renderPage.PageType = "index"
-
- s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req))
-
- /*
- if req.URL != nil &&
- req.URL.Path != "" &&
- req.URL.Path != "/" &&
- req.URL.Path != "/index" &&
- req.URL.Path != "/index.html" {
- resp.WriteHeader(http.StatusNotFound)
- }
- */
-
- client.Req = req
- remAddrPort = req.RemoteAddr
- if s.isHttp && req.Header.Get(httpRealHdr) != "" {
- remAddrPort = req.Header.Get(httpRealHdr)
- req.Header.Del(httpRealHdr)
- }
- if remAddrPort != "" {
- if nAP, err = netip.ParseAddrPort(remAddrPort); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to parse remote address '%s': %v", req.RemoteAddr, err)
- // Don't return an error in case we're doing weird things like direct socket clients.
- err = nil
- /*
- http.Error(resp, "ERROR: Failed to parse client address", http.StatusInternalServerError)
- return
- */
- }
- client.IP = net.ParseIP(nAP.Addr().String())
- client.Port = nAP.Port()
- }
- client.Headers = XmlHeaders(req.Header)
-
- uaVals = req.Header.Values("User-Agent")
- if uaVals != nil && len(uaVals) > 0 {
- client.Client = make([]*R00tClient, len(uaVals))
- for idx, ua := range uaVals {
- if client.Client[idx], err = NewClient(ua); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to create client for '%s': %v", ua, err)
- http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError)
- return
- }
- }
- }
- if client.Client != nil && len(client.Client) > 0 {
- // Check the passed UAs for a browser. We then change the "default" format if so.
- for _, ua := range client.Client {
- if ua.IsMobile || ua.IsDesktop {
- format = mediaHTML
- break
- }
- }
- }
- renderPage.Info = client
-
- vals = req.URL.Query()
-
- // Determine the format/MIME type of the response.
- if vals.Has("mime") {
- format = req.URL.Query().Get("mime")
- } else {
- if parsedFmts, err = parseAccept(strings.Join(req.Header.Values("Accept"), ",")); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to parse Accept header: %v", err)
- http.Error(
- resp,
- "ERROR: Invalid 'Accept' header value; see RFC 9110 § 12.5.1 and https://www.iana.org/assignments/media-types/media-types.xhtml",
- http.StatusBadRequest,
- )
- return
- }
- if format, err = decideParseAccept(parsedFmts, mediaJSON); err != nil {
- if errors.Is(err, ErrUnsupportedMIME) {
- s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s'.", req.RemoteAddr)
- for mt := range mediaNoIndent {
- okMedia = append(okMedia, mt)
- }
- req.Header.Set("Accept", strings.Join(okMedia, ", "))
- http.Error(resp, "ERROR: No supported MIME type specified; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
- return
- } else {
- s.log.Err("server.Server.handleDefault: Received unknown error choosing an Accept header for '%s': %v", req.RemoteAddr, err)
- http.Error(resp, "ERROR: Unknown error occurred when negotiationg MIME type.", http.StatusInternalServerError)
- return
- }
- }
- }
- s.log.Debug("server.Server.handleDefault: Using format '%s' for '%s'", format, req.RemoteAddr)
- // If it's HTML and they want an include, that needs to be validated too.
- if format == mediaHTML && vals.Has("include") {
- doInclude = true
- if parsedFmts, err = parseAccept(strings.Join(vals["include"], ", ")); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to parse include parameter: %v", err)
- http.Error(
- resp,
- "ERROR: Invalid 'include' parameter value; see RFC 9110 § 12.5.1 and https://www.iana.org/assignments/media-types/media-types.xhtml",
- http.StatusBadRequest,
- )
- return
- }
- if renderPage.RawFmt, err = decideParseAccept(parsedFmts, format); err != nil {
- if errors.Is(err, ErrUnsupportedMIME) {
- s.log.Err("server.Server.handleDefault: No supported MIME type found for '%#v' 'include'.", vals["include"], req.RemoteAddr)
- for mt := range mediaNoIndent {
- okMedia = append(okMedia, mt)
- }
- req.Header.Set("Accept", strings.Join(okMedia, ", "))
- http.Error(resp, "ERROR: No supported MIME type specified for 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
- return
- } else {
- s.log.Err("server.Server.handleDefault: Received unknown error choosing an include format for '%s': %v", req.RemoteAddr, err)
- http.Error(resp, "ERROR: Unknown error occurred when negotiationg MIME type.", http.StatusInternalServerError)
- return
- }
- }
- // The indentation is set below.
- }
-
- // Determine indentation (if the format even supports it).
- if format == mediaHTML {
- if doInclude {
- if _, ok = mediaIndent[renderPage.RawFmt]; ok {
- doIndent = vals.Has("indent")
- if doIndent {
- if req.URL.Query().Get("indent") != "" {
- renderPage.RawIndent = req.URL.Query().Get("indent")
- renderPage.DoRawIndent = true
- }
- }
- } else if _, ok = mediaNoIndent[renderPage.RawFmt]; !ok {
- // It's not a supported MIME.
- s.log.Err("server.Server.handleDefault: Requested MIME type '%s' for '%s' unsupported.", renderPage.RawFmt, req.RemoteAddr)
- for mt := range mediaNoIndent {
- okMedia = append(okMedia, mt)
- }
- req.Header.Set("Accept", strings.Join(okMedia, ", "))
- http.Error(
- resp,
- fmt.Sprintf("ERROR: MIME type '%s' unsupported for 'include'; see Accept header in response for valid types.", renderPage.RawFmt),
- http.StatusNotAcceptable,
- )
- return
- } else {
- // This seems backwards, but "non-indented" formats actually need indenting enabled so their whitespace renders properly.
- renderPage.DoRawIndent = true
- }
- }
- } else {
- if _, ok = mediaIndent[format]; ok {
- doIndent = vals.Has("indent")
- if doIndent {
- if req.URL.Query().Get("indent") != "" {
- indent = req.URL.Query().Get("indent")
- }
- }
- } else if _, ok = mediaNoIndent[format]; !ok {
- // It's not a supported MIME.
- s.log.Err("server.Server.handleDefault: Requested MIME type '%s' for '%s' unsupported.", format, req.RemoteAddr)
- for mt := range mediaNoIndent {
- okMedia = append(okMedia, mt)
- }
- req.Header.Set("Accept", strings.Join(okMedia, ", "))
- http.Error(resp, fmt.Sprintf("ERROR: MIME type '%s' unsupported; see Accept header in response for valid types.", format), http.StatusNotAcceptable)
- return
- }
- }
-
- // Now render the response.
- if format == mediaHTML {
- // This gets special treatment since it's templated.
- resp.Header().Set("Content-Type", "text/html; charset=utf-8")
- if doInclude {
- renderPage.Raw = new(string)
- if doIndent {
- if b, err = mediaIndent[renderPage.RawFmt](client, "", renderPage.RawIndent); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to render indented raw '%s' for '%s': %v", renderPage.RawFmt, req.RemoteAddr, err)
- http.Error(resp, fmt.Sprintf("ERROR: Failed to render 'include' '%s'", renderPage.RawFmt), http.StatusInternalServerError)
- return
- }
- } else {
- if b, err = mediaNoIndent[renderPage.RawFmt](client); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to render raw '%s' for '%s': %v", renderPage.RawFmt, req.RemoteAddr, err)
- http.Error(resp, fmt.Sprintf("ERROR: Failed to render '%s'", renderPage.RawFmt), http.StatusInternalServerError)
- return
- }
- }
- *renderPage.Raw = string(b)
- }
- if err = tpl.ExecuteTemplate(resp, "index", renderPage); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to execute template for '%s': %v", req.RemoteAddr, err)
- http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
- return
- }
- } else {
- resp.Header().Set("Content-Type", format)
- if doIndent {
- // This was already filtered to valid specified MIME above.
- if b, err = mediaIndent[format](client, "", indent); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to render indented '%s' for '%s': %v", format, req.RemoteAddr, err)
- http.Error(resp, fmt.Sprintf("ERROR: Failed to render '%s'", format), http.StatusInternalServerError)
- return
- }
- } else {
- if b, err = mediaNoIndent[format](client); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to render '%s' for '%s': %v", format, req.RemoteAddr, err)
- http.Error(resp, fmt.Sprintf("ERROR: Failed to render '%s'", format), http.StatusInternalServerError)
- return
- }
- }
- if _, err = resp.Write(b); err != nil {
- s.log.Err("server.Server.handleDefault: Failed to serve indented '%s' to '%s': %v", format, req.RemoteAddr, err)
- return
- }
- }
-
- s.log.Debug("server.Server.handleDefault: Handled request:\n%s", spew.Sdump(req))
-
- return
-}
-
-func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
-
var err error
var page *Page
var uas []string
@@ -602,7 +360,7 @@ func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, reqdMimes)
- req.Header["Accept"] = okAcceptMime
+ resp.Header()["Accept"] = okAcceptMime
http.Error(resp, "ERROR: No supported MIME type specified via request 'Accept'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
@@ -627,7 +385,7 @@ func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
if outerFmt, err = decideParseAccept(parsedFmts, outerFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["mime"])
- req.Header["Accept"] = okAcceptMime
+ resp.Header()["Accept"] = okAcceptMime
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'mime'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
@@ -652,7 +410,7 @@ func (s *Server) handleDefaultNew(resp http.ResponseWriter, req *http.Request) {
if includeFmt, err = decideParseAccept(parsedFmts, includeFmt); err != nil {
if errors.Is(err, ErrUnsupportedMIME) {
s.log.Err("server.Server.handleDefault: No supported MIME type found for '%s' via '%#v'.", remAddrPort, params["include"])
- req.Header["Accept"] = okAcceptMime
+ resp.Header()["Accept"] = okAcceptMime
http.Error(resp, "ERROR: No supported MIME type specified via URL parameter 'include'; see 'Accept' header in response for valid types.", http.StatusNotAcceptable)
return
} else {
@@ -791,7 +549,7 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
return
}
} else {
- if b, err = mediaNoIndent[*page.RawFmt](page.Indent); err != nil {
+ if b, err = mediaNoIndent[*page.RawFmt](page.Info); err != nil {
s.log.Err("server.Server.renderHTML: Failed to render to include '%s': %v", *page.RawFmt, err)
http.Error(resp, "ERROR: Failed to render include format", http.StatusInternalServerError)
return
diff --git a/server/tpl/meta.info.html.tpl b/server/tpl/meta.info.html.tpl
index 27b18b3..52e579d 100644
--- a/server/tpl/meta.info.html.tpl
+++ b/server/tpl/meta.info.html.tpl
@@ -11,7 +11,7 @@
{{- if $page.Raw }}
Raw Block ({{ $page.RawFmt }}){{ $linkico }}
- {{- if $page.DoRawIndent }}
+ {{- if $page.DoIndent }}
{{ $page.Raw }}
{{- else }}
{{ $page.Raw }}
@@ -20,36 +20,64 @@
{{- end }}
This is information that your browser sends to identify itself.
+ {{- range $idx, $ua := $page.Info.Client }}
- {{- range $idx, $ua := $page.Info.Client }}
User Agent ({{ $idx }}):
+
+
+
+
+ Identifier
+ Value
+
+
+
+ {{- $flds := $ua.ToMap }}
+ {{- range $fld, $str := $flds }}
+
+ {{ $fld }}
+ {{ $str }}
+
+ {{- end }}
+
+
+
+ {{- /*
+
{{- $flds := $ua.ToMap }}
{{- range $fld, $str := $flds }}
{{ $fld }}: {{ $str }}
{{- end }}
- {{- end }}
+ */}}
+ {{- end }}
These are headers sent along with the request your browser sends for the page's content.
Note that some headers may have multiple values.
-
-
- Header
- Value
-
- {{- range $hdrNm, $hdrVals := $page.Info.Req.Header }}
-
- {{- range $val := $hdrVals }}
- {{ $hdrNm }}
- {{ $val }}
- {{- end }}
-
- {{- end }}
-
+
+
+
+
+ Header
+ Value
+
+
+
+ {{- range $hdrNm, $hdrVals := $page.Info.Req.Header }}
+
+ {{- range $val := $hdrVals }}
+ {{ $hdrNm }}
+ {{ $val }}
+ {{- end }}
+
+ {{- end }}
+
+
+
{{- end }}
diff --git a/server/tpl/meta.top.html.tpl b/server/tpl/meta.top.html.tpl
index f396e5b..1648f79 100644
--- a/server/tpl/meta.top.html.tpl
+++ b/server/tpl/meta.top.html.tpl
@@ -5,8 +5,9 @@
-
+
{{ getTitle $page.PageType }}
+ {{- /*
+ */}}
+
+
+
{{- end }}
diff --git a/server/tpl/usage.html.tpl b/server/tpl/usage.html.tpl
index 0e5ad1c..18a443d 100644
--- a/server/tpl/usage.html.tpl
+++ b/server/tpl/usage.html.tpl
@@ -17,10 +17,10 @@
You can force a specific raw output by specifying the
MIME type via
the Accept
header (RFC 9110 § 12.5.1) , which may be one of:
For example:
Accept: application/json
will return JSON.
@@ -36,8 +36,15 @@
If no selectable MIME type is provided but an
Accept
was given, an error will be returned; specifically, a
406
status code (RFC 9110 § 15.5.7).
- In this case, supported MIME types will be returned in the response's
Accept
header.
-
+ In this case, supported MIME types will be returned in the response's
Accept
header values, e.g.:
+
+
+Accept: application/json
+Accept: application/xml
+Accept: application/yaml
+Accept: text/html
+
+
Note that
Lynx and
Elinks are considered "graphical"
browsers by this program as they are HTML-centric.
@@ -46,7 +53,7 @@
The following parameters control/modify behavior.
{{ $linkico }}
- mime: Specify an explicit MIME type via URL instead of the Accept
header as specified above.
+ mime
: Specify an explicit MIME type via URL instead of the Accept
header as specified above.
This should only be used by clients in which it is impossible or particularly cumbersome to modify/specify headers.
Accept
is more performant.
@@ -56,7 +63,7 @@
- include: Include a <code>
(or <pre>
, depending on if indentation is needed/requested) block in the HTML for the specified MIME type as well.
+ include
: Include a <code>
(or <pre>
, depending on if indentation is needed/requested) block in the HTML for the specified MIME type as well.
Only the first supported instance of this parameter will be used.
@@ -70,7 +77,7 @@
- indent: Enable/specify indentation for JSON and XML output; ignored for others.
+ indent
: Enable/specify indentation for JSON and XML output; ignored for others.
The default is to not indent. (Commonly referred to as "condensed" or "compressed" JSON/XML.)
Only the first specified instance of this parameter will be used.