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