From 95a74e02c77c46cd74fd06df4ed015844a183582 Mon Sep 17 00:00:00 2001 From: Dan Walters Date: Sun, 26 May 2019 12:35:51 -0500 Subject: [PATCH] dlna: use a template to render the root service descriptor For various reasons, it seems to make sense to move away from generating the XML with objects. Namespace support is minimal in go, the objects we have are in an upstream project, and some subtitlties seem likely to cause problems with poorly written clients. This removes the empty , but is otherwise the same output. --- cmd/serve/dlna/dlna.go | 101 ++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/cmd/serve/dlna/dlna.go b/cmd/serve/dlna/dlna.go index 889d5fd0b..889f470af 100644 --- a/cmd/serve/dlna/dlna.go +++ b/cmd/serve/dlna/dlna.go @@ -1,6 +1,7 @@ package dlna import ( + "bytes" "encoding/xml" "fmt" "log" @@ -10,6 +11,7 @@ import ( "os" "strconv" "strings" + "text/template" "time" "github.com/anacrolix/dms/soap" @@ -116,8 +118,7 @@ type server struct { httpListenAddr string httpServeMux *http.ServeMux - rootDeviceUUID string - rootDescXML []byte + RootDeviceUUID string FriendlyName string @@ -153,30 +154,11 @@ func newServer(f fs.Fs, opt *dlnaflags.Options) *server { s.listInterfaces() s.httpServeMux = http.NewServeMux() - s.rootDeviceUUID = makeDeviceUUID(s.FriendlyName) - s.rootDescXML, err = xml.MarshalIndent( - upnp.DeviceDesc{ - SpecVersion: upnp.SpecVersion{Major: 1, Minor: 0}, - Device: upnp.Device{ - DeviceType: rootDeviceType, - FriendlyName: s.FriendlyName, - Manufacturer: "rclone (rclone.org)", - ModelName: rootDeviceModelName, - UDN: s.rootDeviceUUID, - ServiceList: func() (ss []upnp.Service) { - for _, s := range services { - ss = append(ss, s.Service) - } - return - }(), - }, - }, - " ", " ") + s.RootDeviceUUID = makeDeviceUUID(s.FriendlyName) if err != nil { // Contents are hardcoded, so this will never happen in production. log.Panicf("Marshal root descriptor XML: %v", err) } - s.rootDescXML = append([]byte(``), s.rootDescXML...) s.initMux(s.httpServeMux) return s @@ -189,6 +171,69 @@ type UPnPService interface { Unsubscribe(sid string) error } +// Returns rclone version number as the model number. +func (s *server) ModelNumber() string { + return fs.Version +} + +// Template used to generate the root device XML descriptor. +// +// Due to the use of namespaces and various subtleties with device compatibility, +// it turns out to be easier to use a template than to marshal XML. +// +// For rendering, it is passed the server object for context. +var rootDescTmpl = template.Must(template.New("rootDesc").Parse(` + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaServer:1 + {{.FriendlyName}} + rclone (rclone.org) + https://rclone.org/ + rclone + rclone + {{.ModelNumber}} + https://rclone.org/ + 00000000 + {{.RootDeviceUUID}} + + + urn:schemas-upnp-org:service:ContentDirectory:1 + urn:upnp-org:serviceId:ContentDirectory + /static/ContentDirectory.xml + /ctl + + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /static/ConnectionManager.xml + /ctl + + + + / + +`)) + +// Renders the root device descriptor. +func (s *server) rootDescHandler(w http.ResponseWriter, r *http.Request) { + buffer := new(bytes.Buffer) + err := rootDescTmpl.Execute(buffer, s) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + w.Header().Set("content-type", `text/xml; charset="utf-8"`) + w.Header().Set("cache-control", "private, max-age=60") + w.Header().Set("content-length", strconv.FormatInt(int64(buffer.Len()), 10)) + buffer.WriteTo(w) +} + // initServicesMap is called during initialization of the server to prepare some internal datastructures. func (s *server) initServicesMap() { urn, err := upnp.ParseServiceType(services[0].ServiceType) @@ -245,15 +290,7 @@ func (s *server) initMux(mux *http.ServeMux) { return }) - mux.HandleFunc(rootDescPath, func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("content-type", `text/xml; charset="utf-8"`) - w.Header().Set("content-length", fmt.Sprint(len(s.rootDescXML))) - w.Header().Set("server", serverField) - _, err := w.Write(s.rootDescXML) - if err != nil { - fs.Errorf(s, "Failed to serve root descriptor XML: %v", err) - } - }) + mux.HandleFunc(rootDescPath, s.rootDescHandler) mux.Handle("/static/", http.StripPrefix("/static/", withHeader("Cache-Control", "public, max-age=86400", @@ -385,7 +422,7 @@ func (s *server) ssdpInterface(intf net.Interface) { Services: serviceTypes(), Location: advertiseLocationFn, Server: serverField, - UUID: s.rootDeviceUUID, + UUID: s.RootDeviceUUID, NotifyInterval: s.AnnounceInterval, }