@ -21,9 +21,12 @@ import (
"errors"
"errors"
"fmt"
"fmt"
"io"
"io"
"log"
"net/http"
"net/http"
"strconv"
"strconv"
"strings"
"strings"
dac "github.com/123Haynes/go-http-digest-auth-client"
)
)
// curl http://fritz.box:49000/igddesc.xml
// curl http://fritz.box:49000/igddesc.xml
@ -40,7 +43,9 @@ var ErrInvalidSOAPResponse = errors.New("invalid SOAP response")
// Root of the UPNP tree
// Root of the UPNP tree
type Root struct {
type Root struct {
BaseUrl string
BaseUrl string
Device Device ` xml:"device" `
Username string
Password string
Device Device ` xml:"device" `
Services map [ string ] * Service // Map of all services indexed by .ServiceType
Services map [ string ] * Service // Map of all services indexed by .ServiceType
}
}
@ -75,7 +80,7 @@ type Service struct {
SCPDUrl string ` xml:"SCPDURL" `
SCPDUrl string ` xml:"SCPDURL" `
Actions map [ string ] * Action // All actions available on the service
Actions map [ string ] * Action // All actions available on the service
StateVariables [ ] * StateVariable // All state variables available on the service
StateVariables [ ] * StateVariable // All state variables available on the service
}
}
type scpdRoot struct {
type scpdRoot struct {
@ -87,20 +92,24 @@ type scpdRoot struct {
type Action struct {
type Action struct {
service * Service
service * Service
Name string ` xml:"name" `
Name string ` xml:"name" `
Arguments [ ] * Argument ` xml:"argumentList>argument" `
Arguments [ ] * Argument ` xml:"argumentList>argument" `
ArgumentMap map [ string ] * Argument // Map of arguments indexed by .Name
ArgumentMap map [ string ] * Argument // Map of arguments indexed by .Name
}
}
// Returns if the action seems to be a query for information.
// Returns if the action seems to be a query for information.
// This is determined by checking if the action has no input arguments and at least one output argument.
// This is determined by checking if the action has no input arguments and at least one output argument.
func ( a * Action ) IsGetOnly ( ) bool {
func ( a * Action ) IsGetOnly ( ) bool {
for _ , a := range a . Arguments {
if strings . HasPrefix ( a . Name , "Get" ) {
if a . Direction == "in" {
for _ , a := range a . Arguments {
return false
if a . Direction == "in" {
return false
}
}
}
return len ( a . Arguments ) > 0
}
}
return len ( a . Arguments ) > 0
return false
}
}
// An Argument to an action
// An Argument to an action
@ -144,6 +153,26 @@ func (r *Root) load() error {
return r . Device . fillServices ( r )
return r . Device . fillServices ( r )
}
}
func ( r * Root ) loadTr64 ( ) error {
igddesc , err := http . Get (
fmt . Sprintf ( "%s/tr64desc.xml" , r . BaseUrl ) ,
)
if err != nil {
return err
}
dec := xml . NewDecoder ( igddesc . Body )
err = dec . Decode ( r )
if err != nil {
return err
}
r . Services = make ( map [ string ] * Service )
return r . Device . fillServices ( r )
}
// load all service descriptions
// load all service descriptions
func ( d * Device ) fillServices ( r * Root ) error {
func ( d * Device ) fillServices ( r * Root ) error {
d . root = r
d . root = r
@ -200,10 +229,10 @@ func (d *Device) fillServices(r *Root) error {
// Currently only actions without input arguments are supported.
// Currently only actions without input arguments are supported.
func ( a * Action ) Call ( ) ( Result , error ) {
func ( a * Action ) Call ( ) ( Result , error ) {
bodystr := fmt . Sprintf ( `
bodystr := fmt . Sprintf ( `
< ? xml version = ' 1.0 ' encoding = ' utf - 8 ' ? >
< ? xml version = ' 1.0 ' encoding = ' utf - 8 ' ? >
< s : Envelope s : encodingStyle = ' http : //schemas.xmlsoap.org/soap/encoding/' xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>
< s : Envelope s : encodingStyle = ' http : //schemas.xmlsoap.org/soap/encoding/' xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>
< s : Body >
< s : Body >
< u : % s xmlns : u = ' % s ' / >
< u : % s xmlns : u = ' % s ' / >
< / s : Body >
< / s : Body >
< / s : Envelope >
< / s : Envelope >
` , a . Name , a . service . ServiceType )
` , a . Name , a . service . ServiceType )
@ -218,18 +247,19 @@ func (a *Action) Call() (Result, error) {
action := fmt . Sprintf ( "%s#%s" , a . service . ServiceType , a . Name )
action := fmt . Sprintf ( "%s#%s" , a . service . ServiceType , a . Name )
req . Header [ "Content-Type" ] = [ ] string { text_xml }
req . Header . Set ( "Content-Type" , text_xml )
req . Header [ "SoapAction" ] = [ ] string { action }
req . Header . Set ( "SoapAction" , action )
t := dac . NewTransport ( a . service . Device . root . Username , a . service . Device . root . Password )
resp , err := http . DefaultClient . Do ( req )
resp , err := t . RoundTrip ( req )
if err != nil {
if err != nil {
return nil , err
log . Fatalln ( err )
}
}
data := new ( bytes . Buffer )
data := new ( bytes . Buffer )
data . ReadFrom ( resp . Body )
data . ReadFrom ( resp . Body )
// fmt.Printf(data.String())
return a . parseSoapResponse ( data )
return a . parseSoapResponse ( data )
}
}
@ -299,9 +329,11 @@ func convertResult(val string, arg *Argument) (interface{}, error) {
}
}
// Load the services tree from an device.
// Load the services tree from an device.
func LoadServices ( device string , port uint16 ) ( * Root , error ) {
func LoadServices ( device string , port uint16 , username string , password string ) ( * Root , error ) {
var root = & Root {
var root = & Root {
BaseUrl : fmt . Sprintf ( "http://%s:%d" , device , port ) ,
BaseUrl : fmt . Sprintf ( "http://%s:%d" , device , port ) ,
Username : username ,
Password : password ,
}
}
err := root . load ( )
err := root . load ( )
@ -309,5 +341,20 @@ func LoadServices(device string, port uint16) (*Root, error) {
return nil , err
return nil , err
}
}
var rootTr64 = & Root {
BaseUrl : fmt . Sprintf ( "http://%s:%d" , device , port ) ,
Username : username ,
Password : password ,
}
err = rootTr64 . loadTr64 ( )
if err != nil {
return nil , err
}
for k , v := range rootTr64 . Services {
root . Services [ k ] = v
}
return root , nil
return root , nil
}
}