// goupnp is an implementation of a client for various UPnP services. // // For most uses, it is recommended to use the code-generated packages under // github.com/huin/goupnp/dcps. Example use is shown at // http://godoc.org/github.com/huin/goupnp/example // // A commonly used client is internetgateway1.WANPPPConnection1: // http://godoc.org/github.com/huin/goupnp/dcps/internetgateway1#WANPPPConnection1 // // Currently only a couple of schemas have code generated for them from the // UPnP example XML specifications. Not all methods will work on these clients, // because the generated stubs contain the full set of specified methods from // the XML specifications, and the discovered services will likely support a // subset of those methods.
package goupnp import ( ) // ContextError is an error that wraps an error with some context information. type ContextError struct { Context string Err error } func ctxError( error, string) ContextError { return ContextError{ Context: , Err: , } } func ctxErrorf( error, string, ...interface{}) ContextError { return ContextError{ Context: fmt.Sprintf(, ...), Err: , } } func ( ContextError) () string { return fmt.Sprintf("%s: %v", .Context, .Err) } // MaybeRootDevice contains either a RootDevice or an error. type MaybeRootDevice struct { // Identifier of the device. Note that this in combination with Location // uniquely identifies a result from DiscoverDevices. USN string // Set iff Err == nil. Root *RootDevice // The location the device was discovered at. This can be used with // DeviceByURL, assuming the device is still present. A location represents // the discovery of a device, regardless of if there was an error probing it. Location *url.URL // The address from which the device was discovered (if known - otherwise nil). LocalAddr net.IP // Any error encountered probing a discovered device. Err error } // DiscoverDevicesCtx attempts to find targets of the given type. This is // typically the entry-point for this package. searchTarget is typically a URN // in the form "urn:schemas-upnp-org:device:..." or // "urn:schemas-upnp-org:service:...". A single error is returned for errors // while attempting to send the query. An error or RootDevice is returned for // each discovered RootDevice. func ( context.Context, string) ([]MaybeRootDevice, error) { , , := httpuClient() if != nil { return nil, } defer () , := context.WithTimeout(, 2*time.Second) defer () , := ssdp.RawSearch(, , string(), 3) if != nil { return nil, } := make([]MaybeRootDevice, len()) for , := range { := &[] .USN = .Header.Get("USN") , := .Location() if != nil { .Err = ContextError{"unexpected bad location from search", } continue } .Location = if , := DeviceByURLCtx(, ); != nil { .Err = } else { .Root = } if := .Header.Get(httpu.LocalAddressHeader); len() > 0 { .LocalAddr = net.ParseIP() } } return , nil } // DiscoverDevices is the legacy version of DiscoverDevicesCtx, but uses // context.Background() as the context. func ( string) ([]MaybeRootDevice, error) { return DiscoverDevicesCtx(context.Background(), ) } func ( context.Context, *url.URL) (*RootDevice, error) { := .String() := new(RootDevice) if := requestXml(, , DeviceXMLNamespace, ); != nil { return nil, ContextError{fmt.Sprintf("error requesting root device details from %q", ), } } var string if .URLBaseStr != "" { = .URLBaseStr } else { = } , := url.Parse() if != nil { return nil, ContextError{fmt.Sprintf("error parsing location URL %q", ), } } .SetURLBase() return , nil } func ( *url.URL) (*RootDevice, error) { return DeviceByURLCtx(context.Background(), ) } // CharsetReaderDefault specifies the charset reader used while decoding the output // from a UPnP server. It can be modified in an init function to allow for non-utf8 encodings, // but should not be changed after requesting clients. var CharsetReaderDefault func(charset string, input io.Reader) (io.Reader, error) // HTTPClient specifies the http.Client object used when fetching the XML from the UPnP server. // HTTPClient defaults the http.DefaultClient. This may be overridden by the importing application. var HTTPClientDefault = http.DefaultClient func requestXml( context.Context, string, string, interface{}) error { , := context.WithTimeout(, 3*time.Second) defer () , := http.NewRequestWithContext(, http.MethodGet, , nil) if != nil { return } , := HTTPClientDefault.Do() if != nil { return } defer .Body.Close() if .StatusCode != 200 { return fmt.Errorf("goupnp: got response status %s from %q", .Status, ) } := xml.NewDecoder(.Body) .DefaultSpace = .CharsetReader = CharsetReaderDefault return .Decode() }