diff --git a/ecs.go b/ecs.go
index fbd5680..b69c792 100644
--- a/ecs.go
+++ b/ecs.go
@@ -22,11 +22,9 @@ import (
"github.com/antchfx/xmlquery"
)
-func ecsHandler(common map[string]string, doc *xmlquery.Node) (bool, string) {
- timestamp := common["Timestamp"]
-
+func ecsHandler(e Envelope, doc *xmlquery.Node) (bool, string) {
// All actions below are for ECS-related functions.
- switch common["Action"] {
+ switch e.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).
@@ -34,51 +32,52 @@ func ecsHandler(common map[string]string, doc *xmlquery.Node) (bool, string) {
//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(`
- 2018
- POINTS
-
- 0
- %s
- %s`, timestamp, timestamp)
- return formatSuccess(common, custom)
+ e.AddCustomType(Balance{
+ Amount: 2018,
+ Currency: "POINTS",
+ })
+ e.AddKVNode("ForceSyncTime", "0")
+ e.AddKVNode("ExtTicketTime", e.Timestamp())
+ e.AddKVNode("SyncTime", e.Timestamp())
+ break
case "NotifyETicketsSynced":
// This is a disgusting request, but 20 dollars is 20 dollars. ;3
fmt.Println("The request is valid! Responding...")
- return formatSuccess(common, "")
+ break
case "ListETickets":
// that's all you've got for me? ;3
fmt.Println("The request is valid! Responding...")
- custom := fmt.Sprintf(`0
- %s
- %s`, timestamp, timestamp)
- return formatSuccess(common, custom)
+ e.AddKVNode("ForceSyncTime", "0")
+ e.AddKVNode("ExtTicketTime", e.Timestamp())
+ e.AddKVNode("SyncTime", e.Timestamp())
+ break
case "PurchaseTitle":
// If you wanna fun time, it's gonna cost ya extra sweetie... ;3
fmt.Println("The request is valid! Responding...")
- custom := fmt.Sprintf(`
- 2018
- POINTS
-
-
- 00000000
- %s
- PURCHGAME
-
- %s
- 00000000
- 00000000
- 00000000
- 00000000`, timestamp, timestamp)
- return formatSuccess(common, custom)
+ e.AddCustomType(Balance{
+ Amount: 2018,
+ Currency: "POINTS",
+ })
+ e.AddCustomType(Transactions{
+ TransactionId: "00000000",
+ Date: e.Timestamp(),
+ Type: "PURCHGAME",
+ })
+ e.AddKVNode("SyncTime", e.Timestamp())
+ e.AddKVNode("Certs", "00000000")
+ e.AddKVNode("TitleId", "00000000")
+ e.AddKVNode("ETickets", "00000000")
+ break
default:
return false, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer."
}
+
+ return e.ReturnSuccess()
}
diff --git a/ias.go b/ias.go
index f813e8d..850eaa6 100644
--- a/ias.go
+++ b/ias.go
@@ -22,72 +22,78 @@ import (
"github.com/antchfx/xmlquery"
)
-func iasHandler(common map[string]string, doc *xmlquery.Node) (bool, string) {
+func iasHandler(e Envelope, doc *xmlquery.Node) (bool, string) {
// All actions below are for IAS-related functions.
- switch common["Action"] {
+ switch e.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":
serialNo, err := getKey(doc, "SerialNumber")
if err != nil {
- return formatError(common, 5, "not good enough for me. ;3", err)
+ return e.ReturnError(5, "not good enough for me. ;3", err)
}
fmt.Println("The request is valid! Responding...")
- custom := fmt.Sprintf(`%s
- R`, serialNo)
- return formatSuccess(common, custom)
+ e.AddKVNode("OriginalSerialNumber", serialNo)
+ e.AddKVNode("DeviceStatus", "R")
+ break
+
+ case "GetChallenge":
+ fmt.Println("The request is valid! Responding...")
+ break
case "GetRegistrationInfo":
accountId, err := getKey(doc, "AccountId")
if err != nil {
- return formatError(common, 6, "how dirty. ;3", err)
+ return e.ReturnError(7, "how dirty. ;3", err)
}
country, err := getKey(doc, "Country")
if err != nil {
- return formatError(common, 6, "how dirty. ;3", err)
+ return e.ReturnError(7, "how dirty. ;3", err)
}
fmt.Println("The request is valid! Responding...")
- custom := fmt.Sprintf(`%s
- 00000000
- false
- %s
-
- 0000000000000000
- R
- POINTS`, accountId, country)
- return formatSuccess(common, custom)
+ e.AddKVNode("AccountId", accountId)
+ e.AddKVNode("DeviceToken", "00000000")
+ e.AddKVNode("DeviceTokenExpired", "false")
+ e.AddKVNode("Country", country)
+ e.AddKVNode("ExtAccountId", "")
+ e.AddKVNode("DeviceCode", "0000000000000000")
+ e.AddKVNode("DeviceStatus", "R")
+ // This _must_ be POINTS.
+ e.AddKVNode("Currency", "POINTS")
+ break
case "Register":
accountId, err := getKey(doc, "AccountId")
if err != nil {
- return formatError(common, 7, "disgustingly invalid. ;3", err)
+ return e.ReturnError(8, "disgustingly invalid. ;3", err)
}
country, err := getKey(doc, "Country")
if err != nil {
- return formatError(common, 7, "disgustingly invalid. ;3", err)
+ return e.ReturnError(8, "disgustingly invalid. ;3", err)
}
fmt.Println("The request is valid! Responding...")
- custom := fmt.Sprintf(`%s
- 00000000
- %s
-
- 00000000`, accountId, country)
- return formatSuccess(common, custom)
+ e.AddKVNode("AccountId", accountId)
+ e.AddKVNode("DeviceToken", "00000000")
+ e.AddKVNode("Country", country)
+ e.AddKVNode("ExtAccountId", "")
+ e.AddKVNode("DeviceCode", "0000000000000000")
+ break
case "Unregister":
// how abnormal... ;3
-
fmt.Println("The request is valid! Responding...")
- return formatSuccess(common, "")
+ break
default:
return false, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer."
}
+
+ return e.ReturnSuccess()
}
diff --git a/main.go b/main.go
index 03872f0..a025992 100644
--- a/main.go
+++ b/main.go
@@ -28,28 +28,6 @@ import (
"strings"
)
-const (
- // Header is the base format of a SOAP response with string substitutions available.
- // All XML constants must be treated as temporary until a proper XPath solution is investigated.
- Header = `
-
-
-<%sResponse xmlns="%s">` + "\n"
- // Template describes common fields across all requests, for easy replication.
- Template = ` %s
- %s
- %s
- %s
- %d
- false` + "\n"
- // Footer is the base format of a closing envelope in SOAP.
- Footer = `%sResponse>
-
-`
-)
-
// checkError makes error handling not as ugly and inefficient.
func checkError(err error) {
if err != nil {
@@ -123,23 +101,24 @@ func commonHandler(w http.ResponseWriter, r *http.Request) {
return
}
+ fmt.Println("Received:", string(body))
+
+ // Insert the current action being performed.
+ envelope := NewEnvelope(service, action)
+
// Extract shared values from this request.
- common, err := obtainCommon(doc)
+ err = envelope.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
+ var result string
if service == "ias" {
- successful, result = iasHandler(common, doc)
+ successful, result = iasHandler(envelope, doc)
} else if service == "ecs" {
- successful, result = ecsHandler(common, doc)
+ successful, result = ecsHandler(envelope, doc)
}
if successful {
@@ -155,5 +134,5 @@ func commonHandler(w http.ResponseWriter, r *http.Request) {
func printError(w http.ResponseWriter, reason string) {
http.Error(w, reason, http.StatusInternalServerError)
- log.Println("Failed to handle request: " + reason)
+ fmt.Println("Failed to handle request: " + reason)
}
diff --git a/structure.go b/structure.go
index 9e88b6c..fefcb08 100644
--- a/structure.go
+++ b/structure.go
@@ -35,3 +35,66 @@ type Config struct {
SQLPass string `xml:"SQLPass"`
SQLDB string `xml:"SQLDB"`
}
+
+// Envelope represents the root element of any response, soapenv:Envelope.
+type Envelope struct {
+ XMLName string `xml:"soapenv:Envelope"`
+ SOAPEnv string `xml:"xmlns:soapenv,attr"`
+ XSD string `xml:"xmlns:xsd,attr"`
+ XSI string `xml:"xmlns:xsi,attr"`
+
+ // Represents a soapenv:Body within.
+ Body Body
+
+ // Used for internal state tracking.
+ action string
+}
+
+// Body represents the nested soapenv:Body element as a child on the root element,
+// containing the response intended for the action being handled.
+type Body struct {
+ XMLName string `xml:"soapenv:Body"`
+
+ // Represents the actual response inside
+ Response Response
+}
+
+// Response describes the inner response format, along with common fields across requests.
+type Response struct {
+ XMLName xml.Name
+ XMLNS string `xml:"xmlns,attr"`
+
+ // These common fields are persistent across all requests.
+ Version string `xml:"Version"`
+ DeviceId string `xml:"DeviceId"`
+ MessageId string `xml:"MessageId"`
+ TimeStamp string `xml:"TimeStamp"`
+ ErrorCode int
+ ServiceStandbyMode bool `xml:"ServiceStandbyMode"`
+
+ // Allows a simple value node to be inserted.
+ KVFields []KVField
+ // Allows for [dynamic content] situations.
+ CustomFields []interface{}
+}
+
+// KVField represents an individual node in form of Contents.
+type KVField struct {
+ XMLName xml.Name
+ Value string `xml:",chardata"`
+}
+
+// Balance represents a common XML structure.
+type Balance struct {
+ XMLName xml.Name `xml:"Balance"`
+ Amount int `xml:"Amount"`
+ Currency string `xml"Currency"`
+}
+
+// Transactions represents a common XML structure.
+type Transactions struct {
+ XMLName xml.Name `xml:"Transactions"`
+ TransactionId string `xml:"TransactionId"`
+ Date string `xml:"Date"`
+ Type string `xml:"Type"`
+}
diff --git a/utils.go b/utils.go
index b0b944c..c2552bc 100644
--- a/utils.go
+++ b/utils.go
@@ -18,8 +18,8 @@
package main
import (
+ "encoding/xml"
"errors"
- "fmt"
"github.com/antchfx/xmlquery"
"io"
"regexp"
@@ -44,41 +44,104 @@ func parseAction(original string) (string, string) {
return service, action
}
-// formatHeader formats a response type and the proper service.
-func formatHeader(responseType string, service string) string {
- return fmt.Sprintf(Header, responseType, "urn:"+service+".wsapi.broadon.com")
+// NewEnvelope returns a new Envelope with proper attributes initialized.
+func NewEnvelope(service string, action string) Envelope {
+ // Get a sexy new timestamp to use.
+ timestampNano := strconv.FormatInt(time.Now().UTC().Unix(), 10) + "000"
+
+ return Envelope{
+ SOAPEnv: "http://schemas.xmlsoap.org/soap/envelope/",
+ XSD: "http://www.w3.org/2001/XMLSchema",
+ XSI: "http://www.w3.org/2001/XMLSchema-instance",
+ Body: Body{
+ Response: Response{
+ XMLName: xml.Name{Local: action + "Response"},
+ XMLNS: "urn:" + service + ".wsapi.broadon.com",
+
+ TimeStamp: timestampNano,
+ },
+ },
+ action: action,
+ }
}
-// formatTemplate inserts common, cross-requests values into every request.
-func formatTemplate(common map[string]string, errorCode int) string {
- return fmt.Sprintf(Template, common["Version"], common["DeviceID"], common["MessageId"], common["Timestamp"], errorCode)
+// Action returns the action for this service.
+func (e *Envelope) Action() string {
+ return e.action
}
-// formatFooter formats the closing tags of any SOAP request per previous response type.
-func formatFooter(responseType string) string {
- return fmt.Sprintf(Footer, responseType)
+// Timestamp returns a shared timestamp for this request.
+func (e *Envelope) Timestamp() string {
+ return e.Body.Response.TimeStamp
}
-// formatForNamespace mangles together several variables throughout a SOAP request.
-func formatForNamespace(common map[string]string, errorCode int, extraContents string) string {
- return fmt.Sprintf("%s%s%s%s",
- formatHeader(common["Action"], common["Service"]),
- formatTemplate(common, errorCode),
- "\t"+extraContents+"\n",
- formatFooter(common["Action"]),
- )
+// obtainCommon interprets a given node, and updates the envelope with common key values.
+func (e *Envelope) ObtainCommon(doc *xmlquery.Node) error {
+ var err error
+
+ // These fields are common across all requests.
+ e.Body.Response.Version, err = getKey(doc, "Version")
+ if err != nil {
+ return err
+ }
+ e.Body.Response.DeviceId, err = getKey(doc, "DeviceId")
+ if err != nil {
+ return err
+ }
+ e.Body.Response.MessageId, err = getKey(doc, "MessageId")
+ if err != nil {
+ return err
+ }
+
+ return nil
}
-// formatSuccess returns a standard SOAP response with a positive error code, and additional contents.
-func formatSuccess(common map[string]string, extraContents string) (bool, string) {
- return true, formatForNamespace(common, 0, extraContents)
+// AddKVNode adds a given key by name to a specified value.
+func (e *Envelope) AddKVNode(key string, value string) {
+ e.Body.Response.KVFields = append(e.Body.Response.KVFields, KVField{
+ XMLName: xml.Name{Local: key},
+ Value: value,
+ })
+}
+
+// AddCustomType adds a given key by name to a specified structure.
+func (e *Envelope) AddCustomType(customType interface{}) {
+ e.Body.Response.CustomFields = append(e.Body.Response.CustomFields, customType)
+}
+
+// becomeXML marshals the Envelope object, returning the intended boolean state on success.
+// ..there has to be a better way to do this, TODO.
+func (e *Envelope) becomeXML(intendedStatus bool) (bool, string) {
+ contents, err := xml.Marshal(e)
+ if err != nil {
+ return false, "an error occurred marshalling XML: " + err.Error()
+ } else {
+ // Add XML header on top of existing contents.
+ result := xml.Header + string(contents)
+ return intendedStatus, result
+ }
+}
+
+// ReturnSuccess returns a standard SOAP response with a positive error code.
+func (e *Envelope) ReturnSuccess() (bool, string) {
+ // Ensure the error code is 0.
+ e.Body.Response.ErrorCode = 0
+
+ return e.becomeXML(true)
}
// formatError returns a standard SOAP response with an error code.
-func formatError(common map[string]string, errorCode int, reason string, err error) (bool, string) {
- extra := "" + reason + "\n" +
- "\t" + err.Error() + "\n"
- return false, formatForNamespace(common, errorCode, extra)
+func (e *Envelope) ReturnError(errorCode int, reason string, err error) (bool, string) {
+ e.Body.Response.ErrorCode = errorCode
+
+ // Ensure all additional fields are empty to avoid conflict.
+ e.Body.Response.KVFields = []KVField{}
+ e.Body.Response.CustomFields = nil
+
+ e.AddKVNode("UserReason", reason)
+ e.AddKVNode("ServerReason", err.Error())
+
+ return e.becomeXML(false)
}
// normalise parses a document, returning a document with only the request type's child nodes, stripped of prefix.
@@ -108,31 +171,6 @@ func stripNamespace(node *xmlquery.Node) {
}
}
-// 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)