BUGFIX: Content-Type, Nil Raw Body Edge Case, Links

FIXES:
* There was an edge case where a raw body for HTML would potentially
  result in a nil byte slice exception. This has been fixed.
  (I don't even know if it was possible, I just made sure it wasn't.)
* The links browser is now explicitly returned as HTML and properly
  detected as a "browser".
* Hyperlinks for links, w3m added to Usage page.
* Content-Type is now always set correctly; there were cases where it
  was improperly returning e.g. text/plain for JSON.
This commit is contained in:
brent saner 2024-12-19 02:27:53 -05:00
parent 8a0465a0f4
commit 6b75e17f48
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
5 changed files with 55 additions and 6 deletions

20
README.adoc Normal file
View File

@ -0,0 +1,20 @@
= ClientInfo
r00t^2 <brent.saner@gmail.com>
Last rendered {localdatetime}
:doctype: book
:docinfo: shared
:data-uri:
:imagesdir: images
:sectlinks:
:sectnums:
:sectnumlevels: 7
:toc: preamble
:toc2: left
:idprefix:
:toclevels: 7
//:toclevels: 4
:source-highlighter: rouge
:docinfo: shared

== DOCS
**TODO!**

8
TODO
View File

@ -1,3 +1,7 @@
- suggest browser, OS alternatives (again) - suggest browser, OS alternatives (again)
- link to ipinfo.io for client IP (again) - implement ipinfo.io equiv. myself?
-- or just implement myself - also link to https://www.useragentstring.com/
-- or maybe https://www.whatsmyua.info/
-- see https://www.useragentstring.com/pages/api.php
- note that though lynx and elinks are "graphical", links is considered text. w3m is considered graphical.
- fix the text/plain issue for the json (et. al.) renderer

View File

@ -79,10 +79,15 @@ var (
mediaJSON: json.MarshalIndent, mediaJSON: json.MarshalIndent,
mediaXML: xml.MarshalIndent, mediaXML: xml.MarshalIndent,
} }
// valid MIMEs.
okAcceptMime []string = []string{ okAcceptMime []string = []string{
mediaJSON, mediaJSON,
mediaXML, mediaXML,
mediaYAML, mediaYAML,
mediaHTML, mediaHTML,
} }
// These are actually HTML.
htmlOverride map[string]bool = map[string]bool{
"Links": true,
}
) )

View File

@ -19,6 +19,7 @@ import (
sysd "github.com/coreos/go-systemd/daemon" sysd "github.com/coreos/go-systemd/daemon"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
`github.com/goccy/go-yaml` `github.com/goccy/go-yaml`
`r00t2.io/clientinfo/version`
"r00t2.io/goutils/multierr" "r00t2.io/goutils/multierr"
) )


@ -280,6 +281,8 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {


s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req)) s.log.Debug("server.Server.handleDefault: Handling request:\n%s", spew.Sdump(req))


resp.Header().Set("ClientInfo-Version", version.Ver.Short())

page = &Page{ page = &Page{
Info: &R00tInfo{ Info: &R00tInfo{
Client: nil, Client: nil,
@ -327,6 +330,9 @@ func (s *Server) handleDefault(resp http.ResponseWriter, req *http.Request) {
http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError) http.Error(resp, fmt.Sprintf("ERROR: Failed to parse 'User-Agent' '%s'", ua), http.StatusInternalServerError)
return return
} }
if parsedUA.Name != nil && htmlOverride[*parsedUA.Name] {
parsedUA.IsDesktop = true
}
page.Info.Client = append(page.Info.Client, parsedUA) page.Info.Client = append(page.Info.Client, parsedUA)
} }
} }
@ -470,6 +476,7 @@ func (s *Server) handleAbout(resp http.ResponseWriter, req *http.Request) {


s.log.Debug("server.Server.handleAbout: Handling request:\n%s", spew.Sdump(req)) s.log.Debug("server.Server.handleAbout: Handling request:\n%s", spew.Sdump(req))


resp.Header().Set("ClientInfo-Version", version.Ver.Short())
resp.Header().Set("Content-Type", "text/html; charset=utf-8") resp.Header().Set("Content-Type", "text/html; charset=utf-8")


if err = tpl.ExecuteTemplate(resp, "about", renderPage); err != nil { if err = tpl.ExecuteTemplate(resp, "about", renderPage); err != nil {
@ -495,7 +502,9 @@ func (s *Server) handleUsage(resp http.ResponseWriter, req *http.Request) {


s.log.Debug("server.Server.handleUsage: Handling request:\n%s", spew.Sdump(req)) s.log.Debug("server.Server.handleUsage: Handling request:\n%s", spew.Sdump(req))


resp.Header().Set("ClientInfo-Version", version.Ver.Short())
resp.Header().Set("Content-Type", "text/html; charset=utf-8") resp.Header().Set("Content-Type", "text/html; charset=utf-8")

if err = tpl.ExecuteTemplate(resp, "usage", renderPage); err != nil { if err = tpl.ExecuteTemplate(resp, "usage", renderPage); err != nil {
s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err) s.log.Err("server.Server.handleAbout: Failed to execute template for '%s': %v", req.RemoteAddr, err)
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError) http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
@ -511,6 +520,8 @@ func (s *Server) renderJSON(page *Page, resp http.ResponseWriter) (err error) {


var b []byte var b []byte


resp.Header().Set("Content-Type", "application/json")

if page.DoIndent { if page.DoIndent {
if b, err = json.MarshalIndent(page.Info, "", page.Indent); err != nil { if b, err = json.MarshalIndent(page.Info, "", page.Indent); err != nil {
s.log.Err("server.Server.renderJSON: Failed to render to indented JSON: %v", err) s.log.Err("server.Server.renderJSON: Failed to render to indented JSON: %v", err)
@ -561,10 +572,14 @@ func (s *Server) renderHTML(page *Page, resp http.ResponseWriter) (err error) {
s.log.Err("server.Server.renderHTML: Failed to render to '%s': %v", *page.RawFmt, err) s.log.Err("server.Server.renderHTML: Failed to render to '%s': %v", *page.RawFmt, err)
} }
} }
page.Raw = new(string) if b != nil {
*page.Raw = string(b) page.Raw = new(string)
*page.Raw = string(b)
}
} }


resp.Header().Set("Content-Type", "text/html; charset=utf-8")

if err = tpl.ExecuteTemplate(resp, "index", page); err != nil { if err = tpl.ExecuteTemplate(resp, "index", page); err != nil {
s.log.Err("server.Server.renderHTML: Failed to render template: %v", err) s.log.Err("server.Server.renderHTML: Failed to render template: %v", err)
http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError) http.Error(resp, "ERROR: Failed to render HTML", http.StatusInternalServerError)
@ -591,6 +606,9 @@ func (s *Server) renderXML(page *Page, resp http.ResponseWriter) (err error) {
return return
} }
} }

resp.Header().Set("Content-Type", "application/xml; charset=utf-8")

if _, err = resp.Write(b); err != nil { if _, err = resp.Write(b); err != nil {
s.log.Err("server.Server.renderXML: Failed to send XML: %v", err) s.log.Err("server.Server.renderXML: Failed to send XML: %v", err)
return return
@ -609,6 +627,8 @@ func (s *Server) renderYML(page *Page, resp http.ResponseWriter) (err error) {
return return
} }


resp.Header().Set("Content-Type", "application/yaml")

if _, err = resp.Write(b); err != nil { if _, err = resp.Write(b); err != nil {
s.log.Err("server.Server.renderJSON: Failed to send JSON: %v", err) s.log.Err("server.Server.renderJSON: Failed to send JSON: %v", err)
return return

View File

@ -46,8 +46,8 @@ Accept: text/html
</pre> </pre>
</p> </p>


Note that <a href="https://lynx.invisible-island.net/">Lynx</a> and <a href="http://elinks.or.cz/">Elinks</a> are considered "graphical" Note that <a href="http://links.twibright.com/">Links</a>a>, <a href="https://lynx.invisible-island.net/">Lynx</a>, <a href="http://elinks.or.cz/">Elinks</a>,
browsers by this program as they are HTML-centric. and <a href="https://w3m.sourceforge.net/">W3M</a> are considered "graphical" browsers by this program as they are HTML-centric.
</p> </p>
<p id="usage_params_mod"> <p id="usage_params_mod">
The following parameters control/modify behavior.<a href="#usage_params_mod">{{ $linkico }}</a> The following parameters control/modify behavior.<a href="#usage_params_mod">{{ $linkico }}</a>