mirror of
https://wiilab.wiimart.org/wiimart/WiiSOAP
synced 2025-09-05 21:11:02 +02:00
Refactor HTTP handling logic
This is an extraordinarily large commit, and in hindsight would have benefitied from being written separately. To summarise: - Converts namespace URI parsing to a regex, instead of convoluted string logic - Removes per-request XML structures in favor of normalised XPath queries - Moves common variables for all requests to a k/v map of their name and contents, in order to reduce function parameter count - Changes the name of the ECS action "NotifiedETicketsSynced" to its hardcoded (tenseless) name, "NotifyETicketsSynced" - Improved properly reporting errors to the end user in areas
This commit is contained in:
parent
9088111c4e
commit
e588ba6e68
87
ecs.go
87
ecs.go
@ -18,43 +18,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"github.com/antchfx/xmlquery"
|
||||
)
|
||||
|
||||
func ecsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Figure out the action to handle via header.
|
||||
action := r.Header.Get("SOAPAction")
|
||||
action = parseAction(action, "ecs")
|
||||
func ecsHandler(common map[string]string, doc *xmlquery.Node) (bool, string) {
|
||||
timestamp := common["Timestamp"]
|
||||
|
||||
// Get a sexy new timestamp to use.
|
||||
timestampNano := strconv.FormatInt(time.Now().UTC().Unix(), 10)
|
||||
timestamp := timestampNano + "000"
|
||||
|
||||
fmt.Println("[!] Incoming ECS request.")
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Error reading request body...", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// The switch converts the HTTP Body of the request into a string. There is no need to convert the cases to byte format.
|
||||
switch action {
|
||||
// All actions below are for ECS-related functions.
|
||||
switch common["Action"] {
|
||||
// TODO: Make the case functions cleaner. (e.g. Should the response be a variable?)
|
||||
// TODO: Update the responses so that they query the SQL Database for the proper information (e.g. Device Code, Token, etc).
|
||||
|
||||
case "CheckDeviceStatus":
|
||||
fmt.Println("CDS.")
|
||||
CDS := CDS{}
|
||||
if err = xml.Unmarshal(body, &CDS); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "You need to POST some SOAP from WSC if you wanna get some, honey. ;3")
|
||||
return
|
||||
}
|
||||
fmt.Println(CDS)
|
||||
//You need to POST some SOAP from WSC if you wanna get some, honey. ;3
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
custom := fmt.Sprintf(`<Balance>
|
||||
<Amount>2018</Amount>
|
||||
@ -63,47 +41,26 @@ func ecsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
<ForceSyncTime>0</ForceSyncTime>
|
||||
<ExtTicketTime>%s</ExtTicketTime>
|
||||
<SyncTime>%s</SyncTime>`, timestamp, timestamp)
|
||||
fmt.Fprint(w, formatSuccess("ecs", action, CDS.Version, CDS.DeviceID, CDS.MessageID, custom))
|
||||
return formatSuccess(common, custom)
|
||||
|
||||
case "NotifyETicketsSynced":
|
||||
// This is a disgusting request, but 20 dollars is 20 dollars. ;3
|
||||
|
||||
case "NotifiedETicketsSynced":
|
||||
fmt.Println("NETS")
|
||||
NETS := NETS{}
|
||||
if err = xml.Unmarshal(body, &NETS); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "This is a disgusting request, but 20 dollars is 20 dollars. ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(NETS)
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
fmt.Fprint(w, formatSuccess("ecs", action, NETS.Version, NETS.DeviceID, NETS.MessageID, ""))
|
||||
return formatSuccess(common, "")
|
||||
|
||||
case "ListETickets":
|
||||
fmt.Println("LET")
|
||||
LET := LET{}
|
||||
if err = xml.Unmarshal(body, &LET); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "that's all you got for me? ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(LET)
|
||||
// that's all you've got for me? ;3
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
custom := fmt.Sprintf(`<ForceSyncTime>0</ForceSyncTime>
|
||||
<ExtTicketTime>%s</ExtTicketTime>
|
||||
<SyncTime>%s</SyncTime>`, timestamp, timestamp)
|
||||
fmt.Fprint(w, formatSuccess("ecs", action, LET.Version, LET.DeviceID, LET.MessageID, custom))
|
||||
return formatSuccess(common, custom)
|
||||
|
||||
case "PurchaseTitle":
|
||||
fmt.Println("PT")
|
||||
PT := PT{}
|
||||
if err = xml.Unmarshal(body, &PT); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "if you wanna fun time, its gonna cost ya extra sweetie. ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(PT)
|
||||
// If you wanna fun time, it's gonna cost ya extra sweetie... ;3
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
custom := fmt.Sprintf(`<Balance>
|
||||
<Amount>2018</Amount>
|
||||
@ -119,15 +76,9 @@ func ecsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
<Certs>00000000</Certs>
|
||||
<Certs>00000000</Certs>
|
||||
<TitleId>00000000</TitleId>`, timestamp, timestamp)
|
||||
fmt.Fprint(w, formatSuccess("ecs", action, PT.Version, PT.DeviceID, PT.MessageID, custom))
|
||||
return formatSuccess(common, custom)
|
||||
|
||||
default:
|
||||
fmt.Fprint(w, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer.")
|
||||
return
|
||||
return false, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer."
|
||||
}
|
||||
|
||||
fmt.Println("Delivered response!")
|
||||
|
||||
// TODO: Add NUS and CAS SOAP to the case list.
|
||||
fmt.Println("[!] End of ECS Request.\n")
|
||||
}
|
||||
|
5
go.mod
5
go.mod
@ -2,4 +2,7 @@ module github.com/morenatsu-net/WiiSOAP
|
||||
|
||||
go 1.12
|
||||
|
||||
require github.com/go-sql-driver/mysql v1.5.0
|
||||
require (
|
||||
github.com/antchfx/xmlquery v1.2.4
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
)
|
||||
|
13
go.sum
13
go.sum
@ -1,2 +1,15 @@
|
||||
github.com/antchfx/xmlquery v1.2.4 h1:T/SH1bYdzdjTMoz2RgsfVKbM5uWh3gjDYYepFqQmFv4=
|
||||
github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM=
|
||||
github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0=
|
||||
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI=
|
||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
98
ias.go
98
ias.go
@ -18,53 +18,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"github.com/antchfx/xmlquery"
|
||||
)
|
||||
|
||||
func iasHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Figure out the action to handle via header.
|
||||
action := r.Header.Get("SOAPAction")
|
||||
action = parseAction(action, "ias")
|
||||
func iasHandler(common map[string]string, doc *xmlquery.Node) (bool, string) {
|
||||
|
||||
fmt.Println("[!] Incoming IAS request.")
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Error reading request body...", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// The switch converts the HTTP Body of the request into a string. There is no need to convert the cases to byte format.
|
||||
switch action {
|
||||
// All actions below are for IAS-related functions.
|
||||
switch common["Action"] {
|
||||
// TODO: Make the case functions cleaner. (e.g. Should the response be a variable?)
|
||||
// TODO: Update the responses so that they query the SQL Database for the proper information (e.g. Device Code, Token, etc).
|
||||
|
||||
case "CheckRegistration":
|
||||
fmt.Println("CR.")
|
||||
CR := CR{}
|
||||
if err = xml.Unmarshal(body, &CR); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "not good enough for me. ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
serialNo, err := getKey(doc, "SerialNumber")
|
||||
if err != nil {
|
||||
return formatError(common, 5, "not good enough for me. ;3", err)
|
||||
}
|
||||
fmt.Println(CR)
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
custom := fmt.Sprintf(`<OriginalSerialNumber>%s</OriginalSerialNumber>
|
||||
<DeviceStatus>R</DeviceStatus>`, CR.SerialNo)
|
||||
fmt.Fprint(w, formatSuccess("ias", action, CR.Version, CR.DeviceID, CR.MessageID, custom))
|
||||
<DeviceStatus>R</DeviceStatus>`, serialNo)
|
||||
return formatSuccess(common, custom)
|
||||
|
||||
case "GetRegistrationInfo":
|
||||
fmt.Println("GRI.")
|
||||
GRI := GRI{}
|
||||
if err = xml.Unmarshal(body, &GRI); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "how dirty. ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
accountId, err := getKey(doc, "AccountId")
|
||||
if err != nil {
|
||||
return formatError(common, 6, "how dirty. ;3", err)
|
||||
}
|
||||
fmt.Println(GRI)
|
||||
|
||||
country, err := getKey(doc, "Country")
|
||||
if err != nil {
|
||||
return formatError(common, 6, "how dirty. ;3", err)
|
||||
}
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
custom := fmt.Sprintf(`<AccountId>%s</AccountId>
|
||||
<DeviceToken>00000000</DeviceToken>
|
||||
@ -73,47 +59,35 @@ func iasHandler(w http.ResponseWriter, r *http.Request) {
|
||||
<ExtAccountId></ExtAccountId>
|
||||
<DeviceCode>0000000000000000</DeviceCode>
|
||||
<DeviceStatus>R</DeviceStatus>
|
||||
<Currency>POINTS</Currency>`, GRI.AccountID, GRI.Country)
|
||||
fmt.Fprint(w, formatSuccess("ias", action, GRI.Version, GRI.DeviceID, GRI.MessageID, custom))
|
||||
<Currency>POINTS</Currency>`, accountId, country)
|
||||
return formatSuccess(common, custom)
|
||||
|
||||
case "Register":
|
||||
fmt.Println("REG.")
|
||||
REG := REG{}
|
||||
if err = xml.Unmarshal(body, ®); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "disgustingly invalid. ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
accountId, err := getKey(doc, "AccountId")
|
||||
if err != nil {
|
||||
return formatError(common, 7, "disgustingly invalid. ;3", err)
|
||||
}
|
||||
fmt.Println(REG)
|
||||
|
||||
country, err := getKey(doc, "Country")
|
||||
if err != nil {
|
||||
return formatError(common, 7, "disgustingly invalid. ;3", err)
|
||||
}
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
custom := fmt.Sprintf(`<AccountId>%s</AccountId>
|
||||
<DeviceToken>00000000</DeviceToken>
|
||||
<Country>%s</Country>
|
||||
<ExtAccountId></ExtAccountId>
|
||||
<DeviceCode>00000000</DeviceCode>`, REG.AccountID, REG.Country)
|
||||
fmt.Fprint(w, formatSuccess("ias", action, REG.Version, REG.DeviceID, REG.MessageID, custom))
|
||||
<DeviceCode>00000000</DeviceCode>`, accountId, country)
|
||||
return formatSuccess(common, custom)
|
||||
|
||||
case "Unregister":
|
||||
fmt.Println("UNR.")
|
||||
UNR := UNR{}
|
||||
if err = xml.Unmarshal(body, &UNR); err != nil {
|
||||
fmt.Println("...or not. Bad or incomplete request. (End processing.)")
|
||||
fmt.Fprint(w, "how abnormal... ;3")
|
||||
fmt.Printf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(UNR)
|
||||
// how abnormal... ;3
|
||||
|
||||
fmt.Println("The request is valid! Responding...")
|
||||
fmt.Fprint(w, formatSuccess("ias", action, UNR.Version, UNR.DeviceID, UNR.MessageID, ""))
|
||||
return formatSuccess(common, "")
|
||||
|
||||
default:
|
||||
fmt.Fprint(w, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer.")
|
||||
return
|
||||
return false, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer."
|
||||
}
|
||||
|
||||
fmt.Println("Delivered response!")
|
||||
|
||||
// TODO: Add NUS and CAS SOAP to the case list.
|
||||
fmt.Println("[!] End of IAS Request.\n")
|
||||
}
|
||||
|
77
main.go
77
main.go
@ -21,11 +21,11 @@ import (
|
||||
"database/sql"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -45,7 +45,7 @@ const (
|
||||
<ErrorCode>%d</ErrorCode>
|
||||
<ServiceStandbyMode>false</ServiceStandbyMode>` + "\n"
|
||||
// Footer is the base format of a closing envelope in SOAP.
|
||||
Footer = ` </%sResponse>
|
||||
Footer = `</%sResponse>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>`
|
||||
)
|
||||
@ -84,9 +84,76 @@ func main() {
|
||||
|
||||
// These following endpoints don't have to match what the official WSC have.
|
||||
// However, semantically, it feels proper.
|
||||
http.HandleFunc("/ecs/services/ECommerceSOAP", ecsHandler) // For ECS operations
|
||||
http.HandleFunc("/ias/services/IdentityAuthenticationSOAP", iasHandler) // For IAS operations
|
||||
http.HandleFunc("/ecs/services/ECommerceSOAP", commonHandler)
|
||||
http.HandleFunc("/ias/services/IdentityAuthenticationSOAP", commonHandler)
|
||||
log.Fatal(http.ListenAndServe(CON.Address, nil))
|
||||
|
||||
// From here on out, all special cool things should go into their respective handler function.
|
||||
}
|
||||
|
||||
func commonHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Figure out the action to handle via header.
|
||||
service, action := parseAction(r.Header.Get("SOAPAction"))
|
||||
if service == "" || action == "" {
|
||||
printError(w, "Error interpreting request...")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify this is a service type we know.
|
||||
switch service {
|
||||
case "ecs":
|
||||
case "ias":
|
||||
break
|
||||
default:
|
||||
printError(w, "Unsupported service type...")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("[!] Incoming " + strings.ToUpper(service) + " request - handling for " + action)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
printError(w, "Error reading request body...")
|
||||
return
|
||||
}
|
||||
|
||||
// Tidy up parsed document for easier usage going forward.
|
||||
doc, err := normalise(service, action, strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
printError(w, "Error interpreting request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Extract shared values from this request.
|
||||
common, err := obtainCommon(doc)
|
||||
if err != nil {
|
||||
printError(w, "Error handling request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Insert the current action being performed.
|
||||
common["Service"] = service
|
||||
common["Action"] = action
|
||||
|
||||
var result string
|
||||
var successful bool
|
||||
if service == "ias" {
|
||||
successful, result = iasHandler(common, doc)
|
||||
} else if service == "ecs" {
|
||||
successful, result = ecsHandler(common, doc)
|
||||
}
|
||||
|
||||
if successful {
|
||||
// Write returned with proper Content-Type
|
||||
w.Header().Set("Content-Type", "text/xml; charset=utf-8")
|
||||
w.Write([]byte(result))
|
||||
} else {
|
||||
printError(w, result)
|
||||
}
|
||||
|
||||
fmt.Println("[!] End of " + strings.ToUpper(service) + " Request.\n")
|
||||
}
|
||||
|
||||
func printError(w http.ResponseWriter, reason string) {
|
||||
http.Error(w, reason, http.StatusInternalServerError)
|
||||
log.Println("Failed to handle request: " + reason)
|
||||
}
|
||||
|
69
structure.go
69
structure.go
@ -35,72 +35,3 @@ type Config struct {
|
||||
SQLPass string `xml:"SQLPass"`
|
||||
SQLDB string `xml:"SQLDB"`
|
||||
}
|
||||
|
||||
// CDS - CheckDeviceStatus
|
||||
type CDS struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>CheckDeviceStatus>Version"`
|
||||
DeviceID string `xml:"Body>CheckDeviceStatus>DeviceId"`
|
||||
MessageID string `xml:"Body>CheckDeviceStatus>MessageId"`
|
||||
}
|
||||
|
||||
// NETS - NotifiedETicketsSynced
|
||||
type NETS struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>NotifiedETicketsSynced>Version"`
|
||||
DeviceID string `xml:"Body>NotifiedETicketsSynced>DeviceId"`
|
||||
MessageID string `xml:"Body>NotifiedETicketsSynced>MessageId"`
|
||||
}
|
||||
|
||||
// LET - ListETickets
|
||||
type LET struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>ListETickets>Version"`
|
||||
DeviceID string `xml:"Body>ListETickets>DeviceId"`
|
||||
MessageID string `xml:"Body>ListETickets>MessageId"`
|
||||
}
|
||||
|
||||
// PT - PurchaseTitle
|
||||
type PT struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>PurchaseTitle>Version"`
|
||||
DeviceID string `xml:"Body>PurchaseTitle>DeviceId"`
|
||||
MessageID string `xml:"Body>PurchaseTitle>MessageId"`
|
||||
}
|
||||
|
||||
// CR - CheckRegistration
|
||||
type CR struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>CheckRegistration>Version"`
|
||||
DeviceID string `xml:"Body>CheckRegistration>DeviceId"`
|
||||
MessageID string `xml:"Body>CheckRegistration>MessageId"`
|
||||
SerialNo string `xml:"Body>CheckRegistration>SerialNumber"`
|
||||
}
|
||||
|
||||
// GRI - GetRegistrationInfo
|
||||
type GRI struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>GetRegistrationInfo>Version"`
|
||||
DeviceID string `xml:"Body>GetRegistrationInfo>DeviceId"`
|
||||
MessageID string `xml:"Body>GetRegistrationInfo>MessageId"`
|
||||
AccountID string `xml:"Body>GetRegistrationInfo>AccountId"`
|
||||
Country string `xml:"Body>GetRegistrationInfo>Country"`
|
||||
}
|
||||
|
||||
// REG - Register
|
||||
type REG struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>Register>Version"`
|
||||
DeviceID string `xml:"Body>Register>DeviceId"`
|
||||
MessageID string `xml:"Body>Register>MessageId"`
|
||||
AccountID string `xml:"Body>Register>AccountId"`
|
||||
Country string `xml:"Body>Register>Country"`
|
||||
}
|
||||
|
||||
// UNR - Unregister
|
||||
type UNR struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Version string `xml:"Body>Unregister>Version"`
|
||||
DeviceID string `xml:"Body>Unregister>DeviceId"`
|
||||
MessageID string `xml:"Body>Unregister>MessageId"`
|
||||
}
|
||||
|
125
utils.go
125
utils.go
@ -18,42 +18,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/antchfx/xmlquery"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// namespaceForType returns the expected XML namespace format for a service.
|
||||
func namespaceForType(service string) string {
|
||||
return "urn:" + service + ".wsapi.broadon.com"
|
||||
}
|
||||
var namespaceParse = regexp.MustCompile(`^urn:(.{3})\.wsapi\.broadon\.com/(.*)$`)
|
||||
|
||||
// parseAction interprets contents along the lines of "urn:ecs.wsapi.broadon.com/CheckDeviceStatus".
|
||||
func parseAction(original string, service string) string {
|
||||
prefix := namespaceForType(service) + "/"
|
||||
stripped := strings.Replace(original, prefix, "", 1)
|
||||
|
||||
if stripped == original {
|
||||
// This doesn't appear valid.
|
||||
return ""
|
||||
} else {
|
||||
return stripped
|
||||
// parseAction interprets contents along the lines of "urn:ecs.wsapi.broadon.com/CheckDeviceStatus",
|
||||
//where "CheckDeviceStatus" is the action to be performed.
|
||||
func parseAction(original string) (string, string) {
|
||||
// Intended to return the original string, the service's name and the name of the action.
|
||||
matches := namespaceParse.FindStringSubmatch(original)
|
||||
if len(matches) != 3 {
|
||||
// It seems like the passed action was not matched properly.
|
||||
return "", ""
|
||||
}
|
||||
|
||||
service := matches[1]
|
||||
action := matches[2]
|
||||
return service, action
|
||||
}
|
||||
|
||||
// formatHeader formats a response type and the proper service.
|
||||
func formatHeader(responseType string, service string) string {
|
||||
return fmt.Sprintf(Header, responseType, namespaceForType(service))
|
||||
return fmt.Sprintf(Header, responseType, "urn:"+service+".wsapi.broadon.com")
|
||||
}
|
||||
|
||||
// formatTemplate inserts common, cross-requests values into every request.
|
||||
func formatTemplate(version string, deviceId string, messageId string, errorCode int) string {
|
||||
// Get a sexy new timestamp to use.
|
||||
timestampNano := strconv.FormatInt(time.Now().UTC().Unix(), 10)
|
||||
timestamp := timestampNano + "000"
|
||||
|
||||
return fmt.Sprintf(Template, version, deviceId, messageId, timestamp, errorCode)
|
||||
func formatTemplate(common map[string]string, errorCode int) string {
|
||||
return fmt.Sprintf(Template, common["Version"], common["DeviceID"], common["MessageId"], common["Timestamp"], errorCode)
|
||||
}
|
||||
|
||||
// formatFooter formats the closing tags of any SOAP request per previous response type.
|
||||
@ -62,21 +60,86 @@ func formatFooter(responseType string) string {
|
||||
}
|
||||
|
||||
// formatForNamespace mangles together several variables throughout a SOAP request.
|
||||
func formatForNamespace(service string, responseType string, version string, deviceId string, messageId string, errorCode int, extraContents string) string {
|
||||
func formatForNamespace(common map[string]string, errorCode int, extraContents string) string {
|
||||
return fmt.Sprintf("%s%s%s%s",
|
||||
formatHeader(responseType, service),
|
||||
formatTemplate(version, deviceId, messageId, errorCode),
|
||||
"\t\t"+extraContents,
|
||||
formatFooter(responseType),
|
||||
formatHeader(common["Action"], common["Service"]),
|
||||
formatTemplate(common, errorCode),
|
||||
"\t"+extraContents+"\n",
|
||||
formatFooter(common["Action"]),
|
||||
)
|
||||
}
|
||||
|
||||
// formatSuccess returns a standard SOAP response with a positive error code, and additional contents.
|
||||
func formatSuccess(service string, responseType string, version string, deviceId string, messageId string, extraContents string) string {
|
||||
return formatForNamespace(service, responseType, version, deviceId, messageId, 0, extraContents)
|
||||
func formatSuccess(common map[string]string, extraContents string) (bool, string) {
|
||||
return true, formatForNamespace(common, 0, extraContents)
|
||||
}
|
||||
|
||||
// formatError returns a standard SOAP response with an error code.
|
||||
func formatError(service string, responseType string, version string, deviceId string, messageId string, errorCode int) string {
|
||||
return formatForNamespace(service, responseType, version, deviceId, messageId, errorCode, "")
|
||||
func formatError(common map[string]string, errorCode int, reason string, err error) (bool, string) {
|
||||
extra := "<UserReason>" + reason + "</UserReason>\n" +
|
||||
"\t<ServerReason>" + err.Error() + "</ServerReason>\n"
|
||||
return false, formatForNamespace(common, errorCode, extra)
|
||||
}
|
||||
|
||||
// normalise parses a document, returning a document with only the request type's child nodes, stripped of prefix.
|
||||
func normalise(service string, action string, reader io.Reader) (*xmlquery.Node, error) {
|
||||
doc, err := xmlquery.Parse(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find the keys for this element named after the action.
|
||||
result := doc.SelectElement("//" + service + ":" + action)
|
||||
if result == nil {
|
||||
return nil, errors.New("missing root node")
|
||||
}
|
||||
stripNamespace(result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// stripNamespace removes a prefix from nodes, changing a key from "ias:Version" to "Version".
|
||||
// It is based off of https://github.com/antchfx/xmlquery/issues/15#issuecomment-567575075.
|
||||
func stripNamespace(node *xmlquery.Node) {
|
||||
node.Prefix = ""
|
||||
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
stripNamespace(child)
|
||||
}
|
||||
}
|
||||
|
||||
// obtainCommon interprets a given node, and from its children finds common keys and respective values across all requests.
|
||||
func obtainCommon(doc *xmlquery.Node) (map[string]string, error) {
|
||||
info := make(map[string]string)
|
||||
|
||||
// Get a sexy new timestamp to use.
|
||||
timestampNano := strconv.FormatInt(time.Now().UTC().Unix(), 10)
|
||||
info["Timestamp"] = timestampNano + "000"
|
||||
|
||||
// These fields are common across all requests.
|
||||
// Looping through all...
|
||||
shared := []string{"Version", "DeviceId", "MessageId"}
|
||||
for _, key := range shared {
|
||||
// select their node by name...
|
||||
value, err := getKey(doc, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// and insert their value to our map.
|
||||
info[key] = value
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// getKey returns the value for a child key from a node, if documented.
|
||||
func getKey(doc *xmlquery.Node, key string) (string, error) {
|
||||
node := xmlquery.FindOne(doc, "//"+key)
|
||||
|
||||
if node == nil {
|
||||
return "", errors.New("missing mandatory key named " + key)
|
||||
} else {
|
||||
return node.InnerText(), nil
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user