From: Sergey Matveev Date: Mon, 20 Dec 2021 12:45:01 +0000 (+0300) Subject: Full HTTP Metalink headers X-Git-Url: http://www.git.stargrave.org/?p=godlighty.git;a=commitdiff_plain;h=75cd1a5bc1ea7eb25a54b397f95b6169a6c99cd0 Full HTTP Metalink headers --- diff --git a/doc/index.texi b/doc/index.texi index cfb612f..750d7f3 100644 --- a/doc/index.texi +++ b/doc/index.texi @@ -48,9 +48,10 @@ read-only @url{https://en.wikipedia.org/wiki/WebDAV, WebDAV} support. @item Per-domain HTTP basic authorization and TLS client authentication. -@item If corresponding @file{.meta4} files are found, then @code{Link} -header is generated automatically to that -@url{http://www.metalinker.org/, Metalink}. +@item If corresponding @file{.meta4} files are found, it is parsed and +additional @code{Link}s with @code{Digest}s headers are generated +automatically, based on that @url{http://www.metalinker.org/, Metalink} +file. @item Very friendly to @url{http://cr.yp.to/daemontools.html, daemontools}. Can drop (UID, GID, groups) privileges. diff --git a/handler.go b/handler.go index 52d9a10..7685981 100644 --- a/handler.go +++ b/handler.go @@ -20,6 +20,7 @@ package godlighty import ( "bytes" "compress/gzip" + "encoding/base64" "errors" "fmt" "io/ioutil" @@ -35,13 +36,13 @@ import ( "time" "github.com/klauspost/compress/zstd" + "go.stargrave.org/godlighty/meta4" "golang.org/x/net/webdav" ) const ( - Index = "index.html" - Readme = "README" - Meta4Ext = ".meta4" + Index = "index.html" + Readme = "README" ) var ( @@ -240,9 +241,35 @@ IndexLookup: } defer fd.Close() - if _, err = os.Stat(pth + Meta4Ext); err == nil { - w.Header().Set("Link", "<"+path.Base(pth)+Meta4Ext+`>; rel=describedby; type="application/metalink4+xml"`) + if meta4fi, err := os.Stat(pth + meta4.Ext); err == nil { + if meta4fi.Size() > meta4.MaxSize { + goto SkipMeta4 + } + meta4Raw, err := ioutil.ReadFile(pth + meta4.Ext) + if err != nil { + goto SkipMeta4 + } + base := path.Base(pth) + forHTTP, err := meta4.Parse(base, meta4Raw) + if err != nil { + goto SkipMeta4 + } + w.Header().Add("Link", "<"+base+meta4.Ext+ + `>; rel=describedby; type="application/metalink4+xml"`, + ) + for _, u := range forHTTP.URLs { + w.Header().Add("Link", "<"+u+">; rel=duplicate") + } + if forHTTP.SHA256 != nil { + w.Header().Add("Digest", "SHA-256="+ + base64.StdEncoding.EncodeToString(forHTTP.SHA256)) + } + if forHTTP.SHA512 != nil { + w.Header().Add("Digest", "SHA-512="+ + base64.StdEncoding.EncodeToString(forHTTP.SHA512)) + } } +SkipMeta4: if contentType == "" { contentType = mediaType(path.Base(pth), cfg.MIMEs) diff --git a/meta4/parse.go b/meta4/parse.go new file mode 100644 index 0000000..92ff37f --- /dev/null +++ b/meta4/parse.go @@ -0,0 +1,60 @@ +/* +godlighty -- highly-customizable HTTP, HTTP/2, HTTPS server +Copyright (C) 2021 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package meta4 + +import ( + "encoding/hex" + "encoding/xml" +) + +type ForHTTP struct { + SHA256 []byte + SHA512 []byte + URLs []string +} + +func Parse(fn string, data []byte) (*ForHTTP, error) { + var meta Metalink + err := xml.Unmarshal(data, &meta) + if err != nil { + return nil, err + } + for _, f := range meta.Files { + if f.Name != fn { + continue + } + forHTTP := ForHTTP{} + for _, h := range f.Hashes { + digest, err := hex.DecodeString(h.Hash) + if err != nil { + return nil, err + } + switch h.Type { + case HashSHA256: + forHTTP.SHA256 = digest + case HashSHA512: + forHTTP.SHA512 = digest + } + } + for _, u := range f.URLs { + forHTTP.URLs = append(forHTTP.URLs, u.URL) + } + return &forHTTP, nil + } + return nil, nil +} diff --git a/meta4/scheme.go b/meta4/scheme.go new file mode 100644 index 0000000..da46e24 --- /dev/null +++ b/meta4/scheme.go @@ -0,0 +1,71 @@ +/* +godlighty -- highly-customizable HTTP, HTTP/2, HTTPS server +Copyright (C) 2021 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package meta4 + +import ( + "encoding/xml" + "time" +) + +const ( + HashSHA256 = "sha-256" + HashSHA512 = "sha-512" + Ext = ".meta4" + MaxSize = 1 << 16 +) + +type Metalink struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:metalink metalink"` + Files []File `xml:"file"` + Generator string `xml:"generator,,omitempty"` + Published time.Time `xml:"published,,omitempty"` +} + +type File struct { + XMLName xml.Name `xml:"file"` + Name string `xml:"name,attr"` + Description string `xml:"description,,omitempty"` + Hashes []Hash `xml:"hash,,omitempty"` + MetaURLs []MetaURL `xml:"metaurl,,omitempty"` + Signature *Signature `xml:"signature"` + Size uint64 `xml:"size,,omitempty"` + URLs []URL `xml:"url,,omitempty"` +} + +type URL struct { + XMLName xml.Name `xml:"url"` + URL string `xml:",chardata"` +} + +type Signature struct { + XMLName xml.Name `xml:"signature"` + MediaType string `xml:"mediatype,attr"` + Signature string `xml:",cdata"` +} + +type Hash struct { + XMLName xml.Name `xml:"hash"` + Type string `xml:"type,attr"` + Hash string `xml:",chardata"` +} + +type MetaURL struct { + XMLName xml.Name `xml:"metaurl"` + MediaType string `xml:"mediatype,attr"` + URL string `xml:",chardata"` +}