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 }}
- {{- 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 | +
---|---|
{{ $fld }} | +{{ $str }} | +
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 | -
---|---|
{{ $hdrNm }} | -{{ $val }} | - {{- end }} -
Header | +Value | +
---|---|
{{ $hdrNm }} | +{{ $val }} | + {{- end }} +
Accept
header (RFC 9110 § 12.5.1), which may be one of:
application/json
for JSONapplication/xml
for XMLapplication/yaml
for YAMLtext/html
for HTMLapplication/json
for JSON (RFC 8259, IANA registration)application/xml
for XML (RFC 7303, IANA registration)application/yaml
for YAML (RFC 9512, IANA registration)text/html
for HTML (RFC 2854, IANA registration)Accept: application/json
will return JSON.
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.
- 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 }}
Accept
header as specified above.
+ mime
: Specify an explicit MIME type via URL instead of the Accept
header as specified above.
Accept
is more performant.<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.
indent
: Enable/specify indentation for JSON and XML output; ignored for others.