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