1package dlna
2
3import (
4	"bytes"
5	"encoding/xml"
6	"fmt"
7	"net"
8	"net/http"
9	"net/url"
10	"os"
11	"strconv"
12	"strings"
13	"time"
14
15	dms_dlna "github.com/anacrolix/dms/dlna"
16	"github.com/anacrolix/dms/soap"
17	"github.com/anacrolix/dms/ssdp"
18	"github.com/anacrolix/dms/upnp"
19	"github.com/rclone/rclone/cmd"
20	"github.com/rclone/rclone/cmd/serve/dlna/data"
21	"github.com/rclone/rclone/cmd/serve/dlna/dlnaflags"
22	"github.com/rclone/rclone/fs"
23	"github.com/rclone/rclone/vfs"
24	"github.com/rclone/rclone/vfs/vfsflags"
25	"github.com/spf13/cobra"
26)
27
28func init() {
29	dlnaflags.AddFlags(Command.Flags())
30	vfsflags.AddFlags(Command.Flags())
31}
32
33// Command definition for cobra.
34var Command = &cobra.Command{
35	Use:   "dlna remote:path",
36	Short: `Serve remote:path over DLNA`,
37	Long: `rclone serve dlna is a DLNA media server for media stored in an rclone remote. Many
38devices, such as the Xbox and PlayStation, can automatically discover this server in the LAN
39and play audio/video from it. VLC is also supported. Service discovery uses UDP multicast
40packets (SSDP) and will thus only work on LANs.
41
42Rclone will list all files present in the remote, without filtering based on media formats or
43file extensions. Additionally, there is no media transcoding support. This means that some
44players might show files that they are not able to play back correctly.
45
46` + dlnaflags.Help + vfs.Help,
47	Run: func(command *cobra.Command, args []string) {
48		cmd.CheckArgs(1, 1, command, args)
49		f := cmd.NewFsSrc(args)
50
51		cmd.Run(false, false, command, func() error {
52			s := newServer(f, &dlnaflags.Opt)
53			if err := s.Serve(); err != nil {
54				return err
55			}
56			s.Wait()
57			return nil
58		})
59	},
60}
61
62const (
63	serverField       = "Linux/3.4 DLNADOC/1.50 UPnP/1.0 DMS/1.0"
64	rootDescPath      = "/rootDesc.xml"
65	resPath           = "/r/"
66	serviceControlURL = "/ctl"
67)
68
69type server struct {
70	// The service SOAP handler keyed by service URN.
71	services map[string]UPnPService
72
73	Interfaces []net.Interface
74
75	HTTPConn       net.Listener
76	httpListenAddr string
77	handler        http.Handler
78
79	RootDeviceUUID string
80
81	FriendlyName string
82
83	// For waiting on the listener to close
84	waitChan chan struct{}
85
86	// Time interval between SSPD announces
87	AnnounceInterval time.Duration
88
89	f   fs.Fs
90	vfs *vfs.VFS
91}
92
93func newServer(f fs.Fs, opt *dlnaflags.Options) *server {
94	friendlyName := opt.FriendlyName
95	if friendlyName == "" {
96		friendlyName = makeDefaultFriendlyName()
97	}
98
99	s := &server{
100		AnnounceInterval: 10 * time.Second,
101		FriendlyName:     friendlyName,
102		RootDeviceUUID:   makeDeviceUUID(friendlyName),
103		Interfaces:       listInterfaces(),
104
105		httpListenAddr: opt.ListenAddr,
106
107		f:   f,
108		vfs: vfs.New(f, &vfsflags.Opt),
109	}
110
111	s.services = map[string]UPnPService{
112		"ContentDirectory": &contentDirectoryService{
113			server: s,
114		},
115		"ConnectionManager": &connectionManagerService{
116			server: s,
117		},
118		"X_MS_MediaReceiverRegistrar": &mediaReceiverRegistrarService{
119			server: s,
120		},
121	}
122
123	// Setup the various http routes.
124	r := http.NewServeMux()
125	r.Handle(resPath, http.StripPrefix(resPath,
126		http.HandlerFunc(s.resourceHandler)))
127	if opt.LogTrace {
128		r.Handle(rootDescPath, traceLogging(http.HandlerFunc(s.rootDescHandler)))
129		r.Handle(serviceControlURL, traceLogging(http.HandlerFunc(s.serviceControlHandler)))
130	} else {
131		r.HandleFunc(rootDescPath, s.rootDescHandler)
132		r.HandleFunc(serviceControlURL, s.serviceControlHandler)
133	}
134	r.Handle("/static/", http.StripPrefix("/static/",
135		withHeader("Cache-Control", "public, max-age=86400",
136			http.FileServer(data.Assets))))
137	s.handler = logging(withHeader("Server", serverField, r))
138
139	return s
140}
141
142// UPnPService is the interface for the SOAP service.
143type UPnPService interface {
144	Handle(action string, argsXML []byte, r *http.Request) (respArgs map[string]string, err error)
145	Subscribe(callback []*url.URL, timeoutSeconds int) (sid string, actualTimeout int, err error)
146	Unsubscribe(sid string) error
147}
148
149// Formats the server as a string (used for logging.)
150func (s *server) String() string {
151	return fmt.Sprintf("DLNA server on %v", s.httpListenAddr)
152}
153
154// Returns rclone version number as the model number.
155func (s *server) ModelNumber() string {
156	return fs.Version
157}
158
159// Renders the root device descriptor.
160func (s *server) rootDescHandler(w http.ResponseWriter, r *http.Request) {
161	tmpl, err := data.GetTemplate()
162	if err != nil {
163		serveError(s, w, "Failed to load root descriptor template", err)
164		return
165	}
166
167	buffer := new(bytes.Buffer)
168	err = tmpl.Execute(buffer, s)
169	if err != nil {
170		serveError(s, w, "Failed to render root descriptor XML", err)
171		return
172	}
173
174	w.Header().Set("content-type", `text/xml; charset="utf-8"`)
175	w.Header().Set("cache-control", "private, max-age=60")
176	w.Header().Set("content-length", strconv.FormatInt(int64(buffer.Len()), 10))
177	_, err = buffer.WriteTo(w)
178	if err != nil {
179		// Network error
180		fs.Debugf(s, "Error writing rootDesc: %v", err)
181	}
182}
183
184// Handle a service control HTTP request.
185func (s *server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
186	soapActionString := r.Header.Get("SOAPACTION")
187	soapAction, err := parseActionHTTPHeader(soapActionString)
188	if err != nil {
189		serveError(s, w, "Could not parse SOAPACTION header", err)
190		return
191	}
192	var env soap.Envelope
193	if err := xml.NewDecoder(r.Body).Decode(&env); err != nil {
194		serveError(s, w, "Could not parse SOAP request body", err)
195		return
196	}
197
198	w.Header().Set("Content-Type", `text/xml; charset="utf-8"`)
199	w.Header().Set("Ext", "")
200	soapRespXML, code := func() ([]byte, int) {
201		respArgs, err := s.soapActionResponse(soapAction, env.Body.Action, r)
202		if err != nil {
203			fs.Errorf(s, "Error invoking %v: %v", soapAction, err)
204			upnpErr := upnp.ConvertError(err)
205			return mustMarshalXML(soap.NewFault("UPnPError", upnpErr)), http.StatusInternalServerError
206		}
207		return marshalSOAPResponse(soapAction, respArgs), http.StatusOK
208	}()
209	bodyStr := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8" standalone="yes"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>%s</s:Body></s:Envelope>`, soapRespXML)
210	w.WriteHeader(code)
211	if _, err := w.Write([]byte(bodyStr)); err != nil {
212		fs.Infof(s, "Error writing response: %v", err)
213	}
214}
215
216// Handle a SOAP request and return the response arguments or UPnP error.
217func (s *server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte, r *http.Request) (map[string]string, error) {
218	service, ok := s.services[sa.Type]
219	if !ok {
220		// TODO: What's the invalid service error?
221		return nil, upnp.Errorf(upnp.InvalidActionErrorCode, "Invalid service: %s", sa.Type)
222	}
223	return service.Handle(sa.Action, actionRequestXML, r)
224}
225
226// Serves actual resources (media files).
227func (s *server) resourceHandler(w http.ResponseWriter, r *http.Request) {
228	remotePath := r.URL.Path
229	node, err := s.vfs.Stat(r.URL.Path)
230	if err != nil {
231		http.NotFound(w, r)
232		return
233	}
234
235	w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10))
236
237	// add some DLNA specific headers
238	if r.Header.Get("getContentFeatures.dlna.org") != "" {
239		w.Header().Set("contentFeatures.dlna.org", dms_dlna.ContentFeatures{
240			SupportRange: true,
241		}.String())
242	}
243	w.Header().Set("transferMode.dlna.org", "Streaming")
244
245	file := node.(*vfs.File)
246	in, err := file.Open(os.O_RDONLY)
247	if err != nil {
248		serveError(node, w, "Could not open resource", err)
249		return
250	}
251	defer fs.CheckClose(in, &err)
252
253	http.ServeContent(w, r, remotePath, node.ModTime(), in)
254}
255
256// Serve runs the server - returns the error only if
257// the listener was not started; does not block, so
258// use s.Wait() to block on the listener indefinitely.
259func (s *server) Serve() (err error) {
260	if s.HTTPConn == nil {
261		s.HTTPConn, err = net.Listen("tcp", s.httpListenAddr)
262		if err != nil {
263			return
264		}
265	}
266
267	go func() {
268		s.startSSDP()
269	}()
270
271	go func() {
272		fs.Logf(s.f, "Serving HTTP on %s", s.HTTPConn.Addr().String())
273
274		err = s.serveHTTP()
275		if err != nil {
276			fs.Logf(s.f, "Error on serving HTTP server: %v", err)
277		}
278	}()
279
280	return nil
281}
282
283// Wait blocks while the listener is open.
284func (s *server) Wait() {
285	<-s.waitChan
286}
287
288func (s *server) Close() {
289	err := s.HTTPConn.Close()
290	if err != nil {
291		fs.Errorf(s.f, "Error closing HTTP server: %v", err)
292		return
293	}
294	close(s.waitChan)
295}
296
297// Run SSDP (multicast for server discovery) on all interfaces.
298func (s *server) startSSDP() {
299	active := 0
300	stopped := make(chan struct{})
301	for _, intf := range s.Interfaces {
302		active++
303		go func(intf2 net.Interface) {
304			defer func() {
305				stopped <- struct{}{}
306			}()
307			s.ssdpInterface(intf2)
308		}(intf)
309	}
310	for active > 0 {
311		<-stopped
312		active--
313	}
314}
315
316// Run SSDP server on an interface.
317func (s *server) ssdpInterface(intf net.Interface) {
318	// Figure out which HTTP location to advertise based on the interface IP.
319	advertiseLocationFn := func(ip net.IP) string {
320		url := url.URL{
321			Scheme: "http",
322			Host: (&net.TCPAddr{
323				IP:   ip,
324				Port: s.HTTPConn.Addr().(*net.TCPAddr).Port,
325			}).String(),
326			Path: rootDescPath,
327		}
328		return url.String()
329	}
330
331	// Note that the devices and services advertised here via SSDP should be
332	// in agreement with the rootDesc XML descriptor that is defined above.
333	ssdpServer := ssdp.Server{
334		Interface: intf,
335		Devices: []string{
336			"urn:schemas-upnp-org:device:MediaServer:1"},
337		Services: []string{
338			"urn:schemas-upnp-org:service:ContentDirectory:1",
339			"urn:schemas-upnp-org:service:ConnectionManager:1",
340			"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"},
341		Location:       advertiseLocationFn,
342		Server:         serverField,
343		UUID:           s.RootDeviceUUID,
344		NotifyInterval: s.AnnounceInterval,
345	}
346
347	// An interface with these flags should be valid for SSDP.
348	const ssdpInterfaceFlags = net.FlagUp | net.FlagMulticast
349
350	if err := ssdpServer.Init(); err != nil {
351		if intf.Flags&ssdpInterfaceFlags != ssdpInterfaceFlags {
352			// Didn't expect it to work anyway.
353			return
354		}
355		if strings.Contains(err.Error(), "listen") {
356			// OSX has a lot of dud interfaces. Failure to create a socket on
357			// the interface are what we're expecting if the interface is no
358			// good.
359			return
360		}
361		fs.Errorf(s, "Error creating ssdp server on %s: %s", intf.Name, err)
362		return
363	}
364	defer ssdpServer.Close()
365	fs.Infof(s, "Started SSDP on %v", intf.Name)
366	stopped := make(chan struct{})
367	go func() {
368		defer close(stopped)
369		if err := ssdpServer.Serve(); err != nil {
370			fs.Errorf(s, "%q: %q\n", intf.Name, err)
371		}
372	}()
373	select {
374	case <-s.waitChan:
375		// Returning will close the server.
376	case <-stopped:
377	}
378}
379
380func (s *server) serveHTTP() error {
381	srv := &http.Server{
382		Handler: s.handler,
383	}
384	err := srv.Serve(s.HTTPConn)
385	select {
386	case <-s.waitChan:
387		return nil
388	default:
389		return err
390	}
391}
392