github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/cmd/serve/dlna/dlna.go (about)

     1  package dlna
     2  
     3  import (
     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  
    28  func init() {
    29  	dlnaflags.AddFlags(Command.Flags())
    30  	vfsflags.AddFlags(Command.Flags())
    31  }
    32  
    33  // Command definition for cobra.
    34  var 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
    38  devices, such as the Xbox and PlayStation, can automatically discover this server in the LAN
    39  and play audio/video from it. VLC is also supported. Service discovery uses UDP multicast
    40  packets (SSDP) and will thus only work on LANs.
    41  
    42  Rclone will list all files present in the remote, without filtering based on media formats or
    43  file extensions. Additionally, there is no media transcoding support. This means that some
    44  players 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  
    62  const (
    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  
    69  type 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  
    93  func 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.
   143  type 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.)
   150  func (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.
   155  func (s *server) ModelNumber() string {
   156  	return fs.Version
   157  }
   158  
   159  // Renders the root device descriptor.
   160  func (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.
   185  func (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.
   217  func (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).
   227  func (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.
   259  func (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.
   284  func (s *server) Wait() {
   285  	<-s.waitChan
   286  }
   287  
   288  func (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.
   298  func (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.
   317  func (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  
   380  func (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  }