diff --git a/lib/config.js b/lib/config.js index 66f8904..dcc47eb 100644 --- a/lib/config.js +++ b/lib/config.js @@ -79,7 +79,6 @@ const options = { XML_FORMAT: { type: "bool", default: false }, XML_NO_DECL: { type: "bool", default: false }, XML_NO_EMPTY: { type: "bool", default: false }, - XML_IGNORE_NAMESPACE: { type: "bool", default: false }, // Should probably never be changed DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 } @@ -106,9 +105,6 @@ function setConfig(name, value, commandLineArgument) { ) setConfig("GPV_BATCH_SIZE", value); - if (name === "IGNORE_XML_NAMESPACES" || name === "ignore-xml-namespaces") - setConfig("XML_IGNORE_NAMESPACE", value); - if (name === "XML_PARSE_IGNORE_ENC" || name === "xml-parse-ignore-enc") setConfig("XML_IGNORE_ENC", value); diff --git a/lib/soap.js b/lib/soap.js index 1426259..cef1ce4 100644 --- a/lib/soap.js +++ b/lib/soap.js @@ -74,18 +74,6 @@ const LIBXMLJS_SAVE_OPTIONS = { selfCloseEmpty: !config.get("XML_NO_EMPTY") }; -const XML_IGNORE_NAMESPACE = config.get("XML_IGNORE_NAMESPACE"); - -if (XML_IGNORE_NAMESPACE) { - libxmljs.Element.prototype.__find = libxmljs.Element.prototype.find; - libxmljs.Element.prototype.find = function(xpath, namespaces) { - const p = xpath - .replace(/([^/:]+:)?([^/]+)(\/|$)/g, "$2$3") - .replace(/([a-zA-Z0-9_-]+)([^/]*)(\/|$)/g, '*[local-name()="$1"]$2$3'); - return libxmljs.Element.prototype.__find.call(this, p, namespaces); - }; -} - let warnings; function parseBool(v) { @@ -97,65 +85,100 @@ function parseBool(v) { } function event(xml) { - return xml.find("EventStruct/EventCode").map(e => e.text().trim()); + return xml + .childNodes() + .filter(n => n.name() === "EventStruct") + .map(c => + c + .childNodes() + .find(n => n.name() === "EventCode") + .text() + .trim() + ); } function parameterInfoList(xml) { - return xml.find("ParameterInfoStruct").map(e => { - const param = e.get("Name").text(); - const value = e.get("Writable").text(); - let parsed = parseBool(value); + return xml + .childNodes() + .filter(e => e.name() === "ParameterInfoStruct") + .map(e => { + let param, value; + for (const c of e.childNodes()) { + switch (c.name()) { + case "Name": + param = c.text(); + break; + case "Writable": + value = c.text(); + break; + } + } - if (parsed === null) { - warnings.push({ - message: "Invalid writable attribute", - parameter: param - }); - parsed = false; - } + let parsed = parseBool(value); - return [param, parsed]; - }); + if (parsed === null) { + warnings.push({ + message: "Invalid writable attribute", + parameter: param + }); + parsed = false; + } + + return [param, parsed]; + }); } function parameterValueList(xml) { - return xml.find("ParameterValueStruct").map(e => { - const valueElement = e.get("Value"); - const valueType = valueElement.attr("type").value(); - const param = e.get("Name").text(); - const value = valueElement.text(); - let parsed = value; - if (valueType === "xsd:boolean") { - parsed = parseBool(value); - if (parsed === null) { - warnings.push({ - message: "Invalid value attribute", - parameter: param - }); - parsed = value; + return xml + .childNodes() + .filter(e => e.name() === "ParameterValueStruct") + .map(e => { + let valueElement, param; + for (const c of e.childNodes()) { + switch (c.name()) { + case "Name": + param = c.text(); + break; + case "Value": + valueElement = c; + break; + } } - } else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") { - parsed = parseInt(value); - if (isNaN(parsed)) { - warnings.push({ - message: "Invalid value attribute", - parameter: param - }); - parsed = value; - } - } else if (valueType === "xsd:dateTime") { - parsed = Date.parse(value); - if (isNaN(parsed)) { - warnings.push({ - message: "Invalid value attribute", - parameter: param - }); - parsed = value; - } - } - return [param, parsed, valueType]; - }); + const valueType = valueElement.attr("type").value(); + const value = valueElement.text(); + let parsed = value; + if (valueType === "xsd:boolean") { + parsed = parseBool(value); + if (parsed === null) { + warnings.push({ + message: "Invalid value attribute", + parameter: param + }); + parsed = value; + } + } else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") { + parsed = parseInt(value); + if (isNaN(parsed)) { + warnings.push({ + message: "Invalid value attribute", + parameter: param + }); + parsed = value; + } + } else if (valueType === "xsd:dateTime") { + parsed = Date.parse(value); + if (isNaN(parsed)) { + warnings.push({ + message: "Invalid value attribute", + parameter: param + }); + parsed = value; + } + } + + return [param, parsed, valueType]; + }); } function GetParameterNames(xml, methodRequest) { @@ -167,7 +190,9 @@ function GetParameterNames(xml, methodRequest) { function GetParameterNamesResponse(xml) { return { name: "GetParameterNamesResponse", - parameterList: parameterInfoList(xml.get("ParameterList")) + parameterList: parameterInfoList( + xml.childNodes().find(n => n.name() === "ParameterList") + ) }; } @@ -183,7 +208,9 @@ function GetParameterValues(xml, methodRequest) { function GetParameterValuesResponse(xml) { return { name: "GetParameterValuesResponse", - parameterList: parameterValueList(xml.get("ParameterList")) + parameterList: parameterValueList( + xml.childNodes().find(n => n.name() === "ParameterList") + ) }; } @@ -220,7 +247,12 @@ function SetParameterValues(xml, methodRequest) { function SetParameterValuesResponse(xml) { return { name: "SetParameterValuesResponse", - status: parseInt(xml.get("Status").text()) + status: parseInt( + xml + .childNodes() + .find(n => n.name() === "Status") + .text() + ) }; } @@ -231,10 +263,22 @@ function AddObject(xml, methodRequest) { } function AddObjectResponse(xml) { + let instanceNumber, status; + for (const c of xml.childNodes()) { + switch (c.name()) { + case "InstanceNumber": + instanceNumber = parseInt(c.text()); + break; + case "Status": + status = parseInt(c.text()); + break; + } + } + return { name: "AddObjectResponse", - instanceNumber: parseInt(xml.get("InstanceNumber").text()), - status: parseInt(xml.get("Status").text()) + instanceNumber: instanceNumber, + status: status }; } @@ -247,7 +291,12 @@ function DeleteObject(xml, methodRequest) { function DeleteObjectResponse(xml) { return { name: "DeleteObjectResponse", - status: parseInt(xml.get("Status").text()) + status: parseInt( + xml + .childNodes() + .find(n => n.name() === "Status") + .text() + ) }; } @@ -287,31 +336,64 @@ function Download(xml, methodRequest) { } function DownloadResponse(xml) { - const res = { - name: "DownloadResponse", - status: parseInt(xml.get("Status").text()) - }; - - if (res.status === 0) { - res.startTime = Date.parse(xml.get("StartTime").text()); - res.completeTime = Date.parse(xml.get("CompleteTime").text()); + let status, startTime, completeTime; + for (const c of xml.childNodes()) { + switch (c.name()) { + case "Status": + status = parseInt(c.text()); + break; + case "StartTime": + startTime = Date.parse(c.text()); + break; + case "CompleteTime": + completeTime = Date.parse(c.text()); + break; + } } - return res; + return { + name: "DownloadResponse", + status: status, + startTime: startTime, + completeTime: completeTime + }; } function Inform(xml) { + let retryCount, evnt, parameterList; + const deviceId = { + Manufacturer: null, + OUI: null, + ProductClass: null, + SerialNumber: null + }; + + for (const c of xml.childNodes()) { + switch (c.name()) { + case "ParameterList": + parameterList = parameterValueList(c); + break; + case "DeviceId": + for (const cc of c.childNodes()) { + const n = cc.name(); + if (n in deviceId) deviceId[n] = cc.text(); + } + break; + case "Event": + evnt = event(c); + break; + case "RetryCount": + retryCount = parseInt(c.text()); + break; + } + } + return { name: "Inform", - parameterList: parameterValueList(xml.get("ParameterList")), - deviceId: { - Manufacturer: xml.get("DeviceId/Manufacturer").text(), - OUI: xml.get("DeviceId/OUI").text(), - ProductClass: xml.get("DeviceId/ProductClass").text(), - SerialNumber: xml.get("DeviceId/SerialNumber").text() - }, - event: event(xml.get("Event")), - retryCount: parseInt(xml.get("RetryCount").text()) + parameterList: parameterList, + deviceId: deviceId, + event: evnt, + retryCount: retryCount }; } @@ -336,12 +418,30 @@ function GetRPCMethodsResponse(xml, methodResponse) { } function TransferComplete(xml) { + let commandKey, _faultStruct, startTime, completeTime; + for (const c of xml.childNodes()) { + switch (c.name()) { + case "CommandKey": + commandKey = c.text(); + break; + case "FaultStruct": + _faultStruct = faultStruct(c); + break; + case "StartTime": + startTime = Date.parse(c.text()); + break; + case "CompleteTime": + completeTime = Date.parse(c.text()); + break; + } + } + return { name: "TransferComplete", - commandKey: xml.get("CommandKey").text(), - faultStruct: faultStruct(xml.get("FaultStruct")), - startTime: Date.parse(xml.get("StartTime").text()), - completeTime: Date.parse(xml.get("CompleteTime").text()) + commandKey: commandKey, + faultStruct: _faultStruct, + startTime: startTime, + completeTime: completeTime }; } @@ -352,7 +452,10 @@ function TransferCompleteResponse(xml) { function RequestDownload(xml) { return { name: "RequestDownload", - fileType: xml.get("FileType").text() + fileType: xml + .childNodes() + .find(n => n.name() === "FileType") + .text() }; } @@ -361,103 +464,125 @@ function RequestDownloadResponse(xml) { } function faultStruct(xml) { - const f = { - faultCode: xml.get("FaultCode").text(), - faultString: xml.get("FaultString").text() - }; - - for (const e of xml.find("SetParameterValuesFault")) { - if (!f.setParameterValuesFault) f.setParameterValuesFault = []; - - f.setParameterValuesFault.push({ - parameterName: e.get("ParameterName").text(), - faultCode: e.get("FaultCode").text(), - faultString: e.get("FaultString").text() - }); + let faultCode, faultString, setParameterValuesFault, pn, fc, fs; + for (const c of xml.childNodes()) { + switch (c.name()) { + case "FaultCode": + faultCode = c.text(); + break; + case "FaultString": + faultString = c.text(); + break; + case "SetParameterValuesFault": + setParameterValuesFault = setParameterValuesFault || []; + for (const cc of c.childNodes()) { + switch (cc.name()) { + case "ParameterName": + pn = cc.text(); + break; + case "FaultCode": + fc = cc.text(); + break; + case "FaultString": + fs = cc.text(); + break; + } + } + setParameterValuesFault.push({ + parameterName: pn, + faultCode: fc, + faultString: fs + }); + } } - return f; + return { faultCode, faultString, setParameterValuesFault }; } -function fault(xml, cwmpVersion) { - return { - faultCode: xml.get("faultcode").text(), - faultString: xml.get("faultstring").text(), - detail: faultStruct(xml.get("detail/cwmp:Fault", NAMESPACES[cwmpVersion])) - }; +function fault(xml) { + let faultCode, faultString, detail; + for (const c of xml.childNodes) { + switch (c.name()) { + case "faultcode": + faultCode = c.text(); + break; + case "faultstring": + faultString = c.text(); + break; + case "detail": + detail = faultStruct(c.childNodes().find(n => n.name() === "Fault")); + break; + } + } + + return { faultCode, faultString, detail }; } function request(data, cwmpVersion, warn) { warnings = warn; const rpc = { - cwmpVersion: cwmpVersion + id: null, + cwmpVersion: cwmpVersion, + sessionTimeout: null, + cpeRequest: null, + cpeFault: null }; if (!data.length) return rpc; const xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS); - let methodElement; + let headerElement, bodyElement; - if (rpc.cwmpVersion) { - methodElement = xml.get( - "/soap-env:Envelope/soap-env:Body/cwmp:*", - NAMESPACES[rpc.cwmpVersion] - ); - } else { - // cwmpVersion not passed, thus it's an inform request - methodElement = xml.get( - "/soap-env:Envelope/soap-env:Body/*", - NAMESPACES["1.0"] - ); - let timeoutElement; - switch (methodElement.namespace().href()) { - case "urn:dslforum-org:cwmp-1-0": - rpc.cwmpVersion = "1.0"; + for (const c of xml.childNodes()) { + switch (c.name()) { + case "Header": + headerElement = c; break; - case "urn:dslforum-org:cwmp-1-1": - rpc.cwmpVersion = "1.1"; + case "Body": + bodyElement = c; break; - case "urn:dslforum-org:cwmp-1-2": - timeoutElement = xml.get( - "/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout", - NAMESPACES["1.2"] - ); - if (timeoutElement) { - rpc.sessionTimeout = parseInt(timeoutElement.text()); - rpc.cwmpVersion = "1.3"; - } else { - rpc.cwmpVersion = "1.2"; - } - break; - case "urn:dslforum-org:cwmp-1-3": - rpc.cwmpVersion = "1.4"; - break; - default: - throw new Error("Unrecognized CWMP version"); - } - - if (!rpc.sessionTimeout) { - timeoutElement = xml.get( - "/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout", - NAMESPACES[rpc.cwmpVersion] - ); - if (timeoutElement) rpc.sessionTimeout = parseInt(timeoutElement.text()); } } - const idElement = xml.get( - "/soap-env:Envelope/soap-env:Header/cwmp:ID", - NAMESPACES[rpc.cwmpVersion] - ); - if (idElement) rpc.id = idElement.text(); + for (const c of headerElement.childNodes()) { + switch (c.name()) { + case "ID": + rpc.id = c.text(); + break; + case "sessionTimeout": + rpc.sessionTimeout = parseInt(c.text()); + break; + } + } - if ( - methodElement && - !(XML_IGNORE_NAMESPACE && methodElement.name() === "Fault") - ) { - switch (methodElement.name()) { + for (const methodElement of bodyElement.childNodes()) { + const methodName = methodElement.name(); + if (methodName === "text") continue; + + if (!rpc.cwmpVersion && methodName !== "Fault") { + switch (methodElement.namespace().href()) { + case "urn:dslforum-org:cwmp-1-0": + rpc.cwmpVersion = "1.0"; + break; + case "urn:dslforum-org:cwmp-1-1": + rpc.cwmpVersion = "1.1"; + break; + case "urn:dslforum-org:cwmp-1-2": + if (rpc.sessionTimeout) rpc.cwmpVersion = "1.3"; + else rpc.cwmpVersion = "1.2"; + + break; + case "urn:dslforum-org:cwmp-1-3": + rpc.cwmpVersion = "1.4"; + break; + default: + throw new Error("Unrecognized CWMP version"); + } + } + + switch (methodName) { case "Inform": rpc.cpeRequest = Inform(methodElement); break; @@ -494,15 +619,12 @@ function request(data, cwmpVersion, warn) { case "DownloadResponse": rpc.cpeResponse = DownloadResponse(methodElement); break; + case "Fault": + rpc.cpeFault = fault(methodElement, rpc.cwmpVersion); + break; default: - throw new Error(`8000 Method not supported ${methodElement.name()}`); + throw new Error(`8000 Method not supported ${methodName}`); } - } else { - const faultElement = xml.get( - "/soap-env:Envelope/soap-env:Body/soap-env:Fault", - NAMESPACES[rpc.cwmpVersion] - ); - rpc.cpeFault = fault(faultElement, rpc.cwmpVersion); } return rpc;