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

     1  package dlna
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/xml"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"net"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"net/http/httputil"
    14  	"os"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/anacrolix/dms/soap"
    20  	"github.com/anacrolix/dms/upnp"
    21  	"github.com/rclone/rclone/fs"
    22  )
    23  
    24  // Return a default "friendly name" for the server.
    25  func makeDefaultFriendlyName() string {
    26  	hostName, err := os.Hostname()
    27  	if err != nil {
    28  		hostName = ""
    29  	} else {
    30  		hostName = " (" + hostName + ")"
    31  	}
    32  	return "rclone" + hostName
    33  }
    34  
    35  func makeDeviceUUID(unique string) string {
    36  	h := md5.New()
    37  	if _, err := io.WriteString(h, unique); err != nil {
    38  		log.Panicf("makeDeviceUUID write failed: %s", err)
    39  	}
    40  	buf := h.Sum(nil)
    41  	return upnp.FormatUUID(buf)
    42  }
    43  
    44  // Get all available active network interfaces.
    45  func listInterfaces() []net.Interface {
    46  	ifs, err := net.Interfaces()
    47  	if err != nil {
    48  		log.Printf("list network interfaces: %v", err)
    49  		return []net.Interface{}
    50  	}
    51  
    52  	var active []net.Interface
    53  	for _, intf := range ifs {
    54  		if intf.Flags&net.FlagUp != 0 && intf.Flags&net.FlagMulticast != 0 && intf.MTU > 0 {
    55  			active = append(active, intf)
    56  		}
    57  	}
    58  	return active
    59  }
    60  
    61  func didlLite(chardata string) string {
    62  	return `<DIDL-Lite` +
    63  		` xmlns:dc="http://purl.org/dc/elements/1.1/"` +
    64  		` xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"` +
    65  		` xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"` +
    66  		` xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/">` +
    67  		chardata +
    68  		`</DIDL-Lite>`
    69  }
    70  
    71  func mustMarshalXML(value interface{}) []byte {
    72  	ret, err := xml.MarshalIndent(value, "", "  ")
    73  	if err != nil {
    74  		log.Panicf("mustMarshalXML failed to marshal %v: %s", value, err)
    75  	}
    76  	return ret
    77  }
    78  
    79  // Marshal SOAP response arguments into a response XML snippet.
    80  func marshalSOAPResponse(sa upnp.SoapAction, args map[string]string) []byte {
    81  	soapArgs := make([]soap.Arg, 0, len(args))
    82  	for argName, value := range args {
    83  		soapArgs = append(soapArgs, soap.Arg{
    84  			XMLName: xml.Name{Local: argName},
    85  			Value:   value,
    86  		})
    87  	}
    88  	return []byte(fmt.Sprintf(`<u:%[1]sResponse xmlns:u="%[2]s">%[3]s</u:%[1]sResponse>`,
    89  		sa.Action, sa.ServiceURN.String(), mustMarshalXML(soapArgs)))
    90  }
    91  
    92  var serviceURNRegexp = regexp.MustCompile(`:service:(\w+):(\d+)$`)
    93  
    94  func parseServiceType(s string) (ret upnp.ServiceURN, err error) {
    95  	matches := serviceURNRegexp.FindStringSubmatch(s)
    96  	if matches == nil {
    97  		err = errors.New(s)
    98  		return
    99  	}
   100  	if len(matches) != 3 {
   101  		log.Panicf("Invalid serviceURNRegexp ?")
   102  	}
   103  	ret.Type = matches[1]
   104  	ret.Version, err = strconv.ParseUint(matches[2], 0, 0)
   105  	return
   106  }
   107  
   108  func parseActionHTTPHeader(s string) (ret upnp.SoapAction, err error) {
   109  	if s[0] != '"' || s[len(s)-1] != '"' {
   110  		return
   111  	}
   112  	s = s[1 : len(s)-1]
   113  	hashIndex := strings.LastIndex(s, "#")
   114  	if hashIndex == -1 {
   115  		return
   116  	}
   117  	ret.Action = s[hashIndex+1:]
   118  	ret.ServiceURN, err = parseServiceType(s[:hashIndex])
   119  	return
   120  }
   121  
   122  type loggingResponseWriter struct {
   123  	http.ResponseWriter
   124  	request   *http.Request
   125  	committed bool
   126  }
   127  
   128  func (lrw *loggingResponseWriter) logRequest(code int, err interface{}) {
   129  	// Choose appropriate log level based on response status code.
   130  	var level fs.LogLevel
   131  	if code < 400 && err == nil {
   132  		level = fs.LogLevelInfo
   133  	} else {
   134  		level = fs.LogLevelError
   135  	}
   136  
   137  	if err == nil {
   138  		err = ""
   139  	}
   140  
   141  	fs.LogPrintf(level, lrw.request.URL, "%s %s %d %s %s",
   142  		lrw.request.RemoteAddr, lrw.request.Method, code,
   143  		lrw.request.Header.Get("SOAPACTION"), err)
   144  }
   145  
   146  func (lrw *loggingResponseWriter) WriteHeader(code int) {
   147  	lrw.committed = true
   148  	lrw.logRequest(code, nil)
   149  	lrw.ResponseWriter.WriteHeader(code)
   150  }
   151  
   152  // HTTP handler that logs requests and any errors or panics.
   153  func logging(next http.Handler) http.Handler {
   154  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   155  		lrw := &loggingResponseWriter{ResponseWriter: w, request: r}
   156  		defer func() {
   157  			err := recover()
   158  			if err != nil {
   159  				if !lrw.committed {
   160  					lrw.logRequest(http.StatusInternalServerError, err)
   161  					http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   162  				} else {
   163  					// Too late to send the error to client, but at least log it.
   164  					fs.Errorf(r.URL.Path, "Recovered panic: %v", err)
   165  				}
   166  			}
   167  		}()
   168  		next.ServeHTTP(lrw, r)
   169  	})
   170  }
   171  
   172  // HTTP handler that logs complete request and response bodies for debugging.
   173  // Error recovery and general request logging are left to logging().
   174  func traceLogging(next http.Handler) http.Handler {
   175  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   176  		dump, err := httputil.DumpRequest(r, true)
   177  		if err != nil {
   178  			serveError(nil, w, "error dumping request", err)
   179  			return
   180  		}
   181  		fs.Debugf(nil, "%s", dump)
   182  
   183  		recorder := httptest.NewRecorder()
   184  		next.ServeHTTP(recorder, r)
   185  
   186  		dump, err = httputil.DumpResponse(recorder.Result(), true)
   187  		if err != nil {
   188  			// log the error but ignore it
   189  			fs.Errorf(nil, "error dumping response: %v", err)
   190  		} else {
   191  			fs.Debugf(nil, "%s", dump)
   192  		}
   193  
   194  		// copy from recorder to the real response writer
   195  		for k, v := range recorder.Header() {
   196  			w.Header()[k] = v
   197  		}
   198  		w.WriteHeader(recorder.Code)
   199  		_, err = recorder.Body.WriteTo(w)
   200  		if err != nil {
   201  			// Network error
   202  			fs.Debugf(nil, "Error writing response: %v", err)
   203  		}
   204  	})
   205  }
   206  
   207  // HTTP handler that sets headers.
   208  func withHeader(name string, value string, next http.Handler) http.Handler {
   209  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   210  		w.Header().Set(name, value)
   211  		next.ServeHTTP(w, r)
   212  	})
   213  }
   214  
   215  // serveError returns an http.StatusInternalServerError and logs the error
   216  func serveError(what interface{}, w http.ResponseWriter, text string, err error) {
   217  	err = fs.CountError(err)
   218  	fs.Errorf(what, "%s: %v", text, err)
   219  	http.Error(w, text+".", http.StatusInternalServerError)
   220  }
   221  
   222  // Splits a path into (root, ext) such that root + ext == path, and ext is empty
   223  // or begins with a period.  Extended version of path.Ext().
   224  func splitExt(path string) (string, string) {
   225  	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
   226  		if path[i] == '.' {
   227  			return path[:i], path[i:]
   228  		}
   229  	}
   230  	return path, ""
   231  }