github.com/divyam234/rclone@v1.64.1/cmd/serve/dlna/dlna.go (about)

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