@0xengine/xmlrpc 0.0.1-security → 1.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @0xengine/xmlrpc might be problematic. Click here for more details.
- package/.travis.yml +6 -0
- package/HISTORY.md +129 -0
- package/LICENSE +23 -0
- package/README.md +204 -3
- package/lib/client.js +177 -0
- package/lib/cookies.js +111 -0
- package/lib/customtype.js +10 -0
- package/lib/date_formatter.js +188 -0
- package/lib/deserializer.js +324 -0
- package/lib/serializer.js +202 -0
- package/lib/server.js +78 -0
- package/lib/validator.js +3 -0
- package/lib/xmlrpc.js +73 -0
- package/package.json +44 -3
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class DateFormatter
|
|
3
|
+
* The DateFormatter supports decoding from and encoding to
|
|
4
|
+
* ISO8601 formatted strings. Accepts formats with and without
|
|
5
|
+
* hyphen/colon separators and correctly parses zoning info.
|
|
6
|
+
*/
|
|
7
|
+
var DateFormatter = function (opts) {
|
|
8
|
+
this.opts = {}
|
|
9
|
+
this.setOpts(opts)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default options for DateFormatter
|
|
14
|
+
* @static
|
|
15
|
+
* @see DateFormatter#setOpts
|
|
16
|
+
*/
|
|
17
|
+
DateFormatter.DEFAULT_OPTIONS = {
|
|
18
|
+
colons: true
|
|
19
|
+
, hyphens: false
|
|
20
|
+
, local: true
|
|
21
|
+
, ms: false
|
|
22
|
+
, offset: false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Regular Expression that disects ISO 8601 formatted strings into
|
|
27
|
+
* an array of parts.
|
|
28
|
+
* @static
|
|
29
|
+
*/
|
|
30
|
+
DateFormatter.ISO8601 = new RegExp(
|
|
31
|
+
'([0-9]{4})([-]?([0-9]{2}))([-]?([0-9]{2}))'
|
|
32
|
+
+ '(T([0-9]{2})(((:?([0-9]{2}))?((:?([0-9]{2}))?(\.([0-9]+))?))?)'
|
|
33
|
+
+ '(Z|([+-]([0-9]{2}(:?([0-9]{2}))?)))?)?'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sets options for encoding Date objects to ISO8601 strings.
|
|
38
|
+
* Omitting the 'opts' argument will reset all options to the default.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} opts - Options (optional)
|
|
41
|
+
* @param {Boolean} opts.colons - Enable/disable formatting the time portion
|
|
42
|
+
* with a colon as separator (default: true)
|
|
43
|
+
* @param {Boolean} opts.hyphens - Enable/disable formatting the date portion
|
|
44
|
+
* with a hyphen as separator (default: false)
|
|
45
|
+
* @param {Boolean} opts.local - Encode as local time instead of UTC
|
|
46
|
+
* (default: true)
|
|
47
|
+
* @param {Boolean} opts.ms - Enable/Disable output of milliseconds
|
|
48
|
+
* (default: false)
|
|
49
|
+
* @param {Boolean} opts.offset - Enable/Disable output of UTC offset
|
|
50
|
+
* (default: false)
|
|
51
|
+
*/
|
|
52
|
+
DateFormatter.prototype.setOpts = function (opts) {
|
|
53
|
+
if (!opts) opts = DateFormatter.DEFAULT_OPTIONS
|
|
54
|
+
|
|
55
|
+
var ctx = this
|
|
56
|
+
Object.keys(DateFormatter.DEFAULT_OPTIONS).forEach(function (k) {
|
|
57
|
+
ctx.opts[k] = opts.hasOwnProperty(k) ?
|
|
58
|
+
opts[k] : DateFormatter.DEFAULT_OPTIONS[k]
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Converts a date time stamp following the ISO8601 format to a JavaScript Date
|
|
64
|
+
* object.
|
|
65
|
+
*
|
|
66
|
+
* @param {String} time - String representation of timestamp.
|
|
67
|
+
* @return {Date} - Date object from timestamp.
|
|
68
|
+
*/
|
|
69
|
+
DateFormatter.prototype.decodeIso8601 = function(time) {
|
|
70
|
+
var dateParts = time.toString().match(DateFormatter.ISO8601)
|
|
71
|
+
if (!dateParts) {
|
|
72
|
+
throw new Error('Expected a ISO8601 datetime but got \'' + time + '\'')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
var date = [
|
|
76
|
+
[dateParts[1], dateParts[3] || '01', dateParts[5] || '01'].join('-')
|
|
77
|
+
, 'T'
|
|
78
|
+
, [
|
|
79
|
+
dateParts[7] || '00'
|
|
80
|
+
, dateParts[11] || '00'
|
|
81
|
+
, dateParts[14] || '00'
|
|
82
|
+
].join(':')
|
|
83
|
+
, '.'
|
|
84
|
+
, dateParts[16] || '000'
|
|
85
|
+
].join('')
|
|
86
|
+
|
|
87
|
+
date += (dateParts[17] !== undefined) ?
|
|
88
|
+
dateParts[17] +
|
|
89
|
+
((dateParts[19] && dateParts[20] === undefined) ? '00' : '') :
|
|
90
|
+
DateFormatter.formatCurrentOffset(new Date(date))
|
|
91
|
+
|
|
92
|
+
return new Date(date)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Converts a JavaScript Date object to an ISO8601 timestamp.
|
|
97
|
+
*
|
|
98
|
+
* @param {Date} date - Date object.
|
|
99
|
+
* @return {String} - String representation of timestamp.
|
|
100
|
+
*/
|
|
101
|
+
DateFormatter.prototype.encodeIso8601 = function(date) {
|
|
102
|
+
var parts = this.opts.local ?
|
|
103
|
+
DateFormatter.getLocalDateParts(date) :
|
|
104
|
+
DateFormatter.getUTCDateParts(date)
|
|
105
|
+
|
|
106
|
+
return [
|
|
107
|
+
[parts[0],parts[1],parts[2]].join(this.opts.hyphens ? '-' : '')
|
|
108
|
+
, 'T'
|
|
109
|
+
, [parts[3],parts[4],parts[5]].join(this.opts.colons ? ':' : '')
|
|
110
|
+
, (this.opts.ms) ? '.' + parts[6] : ''
|
|
111
|
+
, (this.opts.local) ? ((this.opts.offset) ?
|
|
112
|
+
DateFormatter.formatCurrentOffset(date) : '') : 'Z'
|
|
113
|
+
].join('')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Helper function to get an array of zero-padded date parts,
|
|
118
|
+
* in UTC
|
|
119
|
+
*
|
|
120
|
+
* @param {Date} date - Date Object
|
|
121
|
+
* @return {String[]}
|
|
122
|
+
*/
|
|
123
|
+
DateFormatter.getUTCDateParts = function (date) {
|
|
124
|
+
return [
|
|
125
|
+
date.getUTCFullYear()
|
|
126
|
+
, DateFormatter.zeroPad(date.getUTCMonth()+1,2)
|
|
127
|
+
, DateFormatter.zeroPad(date.getUTCDate(),2)
|
|
128
|
+
, DateFormatter.zeroPad(date.getUTCHours(), 2)
|
|
129
|
+
, DateFormatter.zeroPad(date.getUTCMinutes(), 2)
|
|
130
|
+
, DateFormatter.zeroPad(date.getUTCSeconds(), 2)
|
|
131
|
+
, DateFormatter.zeroPad(date.getUTCMilliseconds(), 3)]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Helper function to get an array of zero-padded date parts,
|
|
137
|
+
* in the local time zone
|
|
138
|
+
*
|
|
139
|
+
* @param {Date} date - Date Object
|
|
140
|
+
* @return {String[]}
|
|
141
|
+
*/
|
|
142
|
+
DateFormatter.getLocalDateParts = function (date) {
|
|
143
|
+
return [
|
|
144
|
+
date.getFullYear()
|
|
145
|
+
, DateFormatter.zeroPad(date.getMonth()+1,2)
|
|
146
|
+
, DateFormatter.zeroPad(date.getDate(),2)
|
|
147
|
+
, DateFormatter.zeroPad(date.getHours(), 2)
|
|
148
|
+
, DateFormatter.zeroPad(date.getMinutes(), 2)
|
|
149
|
+
, DateFormatter.zeroPad(date.getSeconds(), 2)
|
|
150
|
+
, DateFormatter.zeroPad(date.getMilliseconds(), 3)]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Helper function to pad the digits with 0s to meet date formatting
|
|
155
|
+
* requirements.
|
|
156
|
+
*
|
|
157
|
+
* @param {Number} digit - The number to pad.
|
|
158
|
+
* @param {Number} length - Length of digit string, prefix with 0s if not
|
|
159
|
+
* already length.
|
|
160
|
+
* @return {String} - String with the padded digit
|
|
161
|
+
*/
|
|
162
|
+
DateFormatter.zeroPad = function (digit, length) {
|
|
163
|
+
var padded = '' + digit
|
|
164
|
+
while (padded.length < length) {
|
|
165
|
+
padded = '0' + padded
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return padded
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Helper function to get the current timezone to default decoding to
|
|
173
|
+
* rather than UTC. (for backward compatibility)
|
|
174
|
+
*
|
|
175
|
+
* @return {String} - in the format /Z|[+-]\d{2}:\d{2}/
|
|
176
|
+
*/
|
|
177
|
+
DateFormatter.formatCurrentOffset = function (d) {
|
|
178
|
+
var offset = (d || new Date()).getTimezoneOffset()
|
|
179
|
+
return (offset === 0) ? 'Z' : [
|
|
180
|
+
(offset < 0) ? '+' : '-'
|
|
181
|
+
, DateFormatter.zeroPad(Math.abs(Math.floor(offset/60)),2)
|
|
182
|
+
, ':'
|
|
183
|
+
, DateFormatter.zeroPad(Math.abs(offset%60),2)
|
|
184
|
+
].join('')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// export an instance of DateFormatter only.
|
|
188
|
+
module.exports = new DateFormatter()
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
var sax = require('sax')
|
|
2
|
+
, dateFormatter = require('./date_formatter')
|
|
3
|
+
|
|
4
|
+
var Deserializer = function(encoding) {
|
|
5
|
+
this.type = null
|
|
6
|
+
this.responseType = null
|
|
7
|
+
this.stack = []
|
|
8
|
+
this.marks = []
|
|
9
|
+
this.data = []
|
|
10
|
+
this.methodname = null
|
|
11
|
+
this.encoding = encoding || 'utf8'
|
|
12
|
+
this.value = false
|
|
13
|
+
this.callback = null
|
|
14
|
+
this.error = null
|
|
15
|
+
|
|
16
|
+
this.parser = sax.createStream()
|
|
17
|
+
this.parser.on('opentag', this.onOpentag.bind(this))
|
|
18
|
+
this.parser.on('closetag', this.onClosetag.bind(this))
|
|
19
|
+
this.parser.on('text', this.onText.bind(this))
|
|
20
|
+
this.parser.on('cdata', this.onCDATA.bind(this))
|
|
21
|
+
this.parser.on('end', this.onDone.bind(this))
|
|
22
|
+
this.parser.on('error', this.onError.bind(this))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Deserializer.prototype.deserializeMethodResponse = function(stream, callback) {
|
|
26
|
+
var that = this
|
|
27
|
+
|
|
28
|
+
this.callback = function(error, result) {
|
|
29
|
+
if (error) {
|
|
30
|
+
callback(error)
|
|
31
|
+
}
|
|
32
|
+
else if (result.length > 1) {
|
|
33
|
+
callback(new Error('Response has more than one param'))
|
|
34
|
+
}
|
|
35
|
+
else if (that.type !== 'methodresponse') {
|
|
36
|
+
callback(new Error('Not a method response'))
|
|
37
|
+
}
|
|
38
|
+
else if (!that.responseType) {
|
|
39
|
+
callback(new Error('Invalid method response'))
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
callback(null, result[0])
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stream.setEncoding(this.encoding)
|
|
47
|
+
stream.on('error', this.onError.bind(this))
|
|
48
|
+
stream.pipe(this.parser)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Deserializer.prototype.deserializeMethodCall = function(stream, callback) {
|
|
52
|
+
var that = this
|
|
53
|
+
|
|
54
|
+
this.callback = function(error, result) {
|
|
55
|
+
if (error) {
|
|
56
|
+
callback(error)
|
|
57
|
+
}
|
|
58
|
+
else if (that.type !== 'methodcall') {
|
|
59
|
+
callback(new Error('Not a method call'))
|
|
60
|
+
}
|
|
61
|
+
else if (!that.methodname) {
|
|
62
|
+
callback(new Error('Method call did not contain a method name'))
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
callback(null, that.methodname, result)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
stream.setEncoding(this.encoding)
|
|
70
|
+
stream.on('error', this.onError.bind(this))
|
|
71
|
+
stream.pipe(this.parser)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Deserializer.prototype.onDone = function() {
|
|
75
|
+
var that = this
|
|
76
|
+
|
|
77
|
+
if (!this.error) {
|
|
78
|
+
if (this.type === null || this.marks.length) {
|
|
79
|
+
this.callback(new Error('Invalid XML-RPC message'))
|
|
80
|
+
}
|
|
81
|
+
else if (this.responseType === 'fault') {
|
|
82
|
+
var createFault = function(fault) {
|
|
83
|
+
var error = new Error('XML-RPC fault' + (fault.faultString ? ': ' + fault.faultString : ''))
|
|
84
|
+
error.code = fault.faultCode
|
|
85
|
+
error.faultCode = fault.faultCode
|
|
86
|
+
error.faultString = fault.faultString
|
|
87
|
+
return error
|
|
88
|
+
}
|
|
89
|
+
this.callback(createFault(this.stack[0]))
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.callback(undefined, this.stack)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// TODO:
|
|
98
|
+
// Error handling needs a little thinking. There are two different kinds of
|
|
99
|
+
// errors:
|
|
100
|
+
// 1. Low level errors like network, stream or xml errors. These don't
|
|
101
|
+
// require special treatment. They only need to be forwarded. The IO
|
|
102
|
+
// is already stopped in these cases.
|
|
103
|
+
// 2. Protocol errors: Invalid tags, invalid values &c. These happen in
|
|
104
|
+
// our code and we should tear down the IO and stop parsing.
|
|
105
|
+
// Currently all errors end here. Guess I'll split it up.
|
|
106
|
+
Deserializer.prototype.onError = function(msg) {
|
|
107
|
+
if (!this.error) {
|
|
108
|
+
if (typeof msg === 'string') {
|
|
109
|
+
this.error = new Error(msg)
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.error = msg
|
|
113
|
+
}
|
|
114
|
+
this.callback(this.error)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
Deserializer.prototype.push = function(value) {
|
|
119
|
+
this.stack.push(value)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//==============================================================================
|
|
123
|
+
// SAX Handlers
|
|
124
|
+
//==============================================================================
|
|
125
|
+
|
|
126
|
+
Deserializer.prototype.onOpentag = function(node) {
|
|
127
|
+
if (node.name === 'ARRAY' || node.name === 'STRUCT') {
|
|
128
|
+
this.marks.push(this.stack.length)
|
|
129
|
+
}
|
|
130
|
+
this.data = []
|
|
131
|
+
this.value = (node.name === 'VALUE')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
Deserializer.prototype.onText = function(text) {
|
|
135
|
+
this.data.push(text)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Deserializer.prototype.onCDATA = function(cdata) {
|
|
139
|
+
this.data.push(cdata)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Deserializer.prototype.onClosetag = function(el) {
|
|
143
|
+
var data = this.data.join('')
|
|
144
|
+
try {
|
|
145
|
+
switch(el) {
|
|
146
|
+
case 'BOOLEAN':
|
|
147
|
+
this.endBoolean(data)
|
|
148
|
+
break
|
|
149
|
+
case 'INT':
|
|
150
|
+
case 'I4':
|
|
151
|
+
this.endInt(data)
|
|
152
|
+
break
|
|
153
|
+
case 'I8':
|
|
154
|
+
this.endI8(data)
|
|
155
|
+
break
|
|
156
|
+
case 'DOUBLE':
|
|
157
|
+
this.endDouble(data)
|
|
158
|
+
break
|
|
159
|
+
case 'STRING':
|
|
160
|
+
case 'NAME':
|
|
161
|
+
this.endString(data)
|
|
162
|
+
break
|
|
163
|
+
case 'ARRAY':
|
|
164
|
+
this.endArray(data)
|
|
165
|
+
break
|
|
166
|
+
case 'STRUCT':
|
|
167
|
+
this.endStruct(data)
|
|
168
|
+
break
|
|
169
|
+
case 'BASE64':
|
|
170
|
+
this.endBase64(data)
|
|
171
|
+
break
|
|
172
|
+
case 'DATETIME.ISO8601':
|
|
173
|
+
this.endDateTime(data)
|
|
174
|
+
break
|
|
175
|
+
case 'VALUE':
|
|
176
|
+
this.endValue(data)
|
|
177
|
+
break
|
|
178
|
+
case 'PARAMS':
|
|
179
|
+
this.endParams(data)
|
|
180
|
+
break
|
|
181
|
+
case 'FAULT':
|
|
182
|
+
this.endFault(data)
|
|
183
|
+
break
|
|
184
|
+
case 'METHODRESPONSE':
|
|
185
|
+
this.endMethodResponse(data)
|
|
186
|
+
break
|
|
187
|
+
case 'METHODNAME':
|
|
188
|
+
this.endMethodName(data)
|
|
189
|
+
break
|
|
190
|
+
case 'METHODCALL':
|
|
191
|
+
this.endMethodCall(data)
|
|
192
|
+
break
|
|
193
|
+
case 'NIL':
|
|
194
|
+
this.endNil(data)
|
|
195
|
+
break
|
|
196
|
+
case 'DATA':
|
|
197
|
+
case 'PARAM':
|
|
198
|
+
case 'MEMBER':
|
|
199
|
+
// Ignored by design
|
|
200
|
+
break
|
|
201
|
+
default:
|
|
202
|
+
this.onError('Unknown XML-RPC tag \'' + el + '\'')
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
this.onError(e)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Deserializer.prototype.endNil = function(data) {
|
|
212
|
+
this.push(null)
|
|
213
|
+
this.value = false
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Deserializer.prototype.endBoolean = function(data) {
|
|
217
|
+
if (data === '1') {
|
|
218
|
+
this.push(true)
|
|
219
|
+
}
|
|
220
|
+
else if (data === '0') {
|
|
221
|
+
this.push(false)
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
throw new Error('Illegal boolean value \'' + data + '\'')
|
|
225
|
+
}
|
|
226
|
+
this.value = false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Deserializer.prototype.endInt = function(data) {
|
|
230
|
+
var value = parseInt(data, 10)
|
|
231
|
+
if (isNaN(value)) {
|
|
232
|
+
throw new Error('Expected an integer but got \'' + data + '\'')
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.push(value)
|
|
236
|
+
this.value = false
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
Deserializer.prototype.endDouble = function(data) {
|
|
241
|
+
var value = parseFloat(data)
|
|
242
|
+
if (isNaN(value)) {
|
|
243
|
+
throw new Error('Expected a double but got \'' + data + '\'')
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
this.push(value)
|
|
247
|
+
this.value = false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Deserializer.prototype.endString = function(data) {
|
|
252
|
+
this.push(data)
|
|
253
|
+
this.value = false
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
Deserializer.prototype.endArray = function(data) {
|
|
257
|
+
var mark = this.marks.pop()
|
|
258
|
+
this.stack.splice(mark, this.stack.length - mark, this.stack.slice(mark))
|
|
259
|
+
this.value = false
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
Deserializer.prototype.endStruct = function(data) {
|
|
263
|
+
var mark = this.marks.pop()
|
|
264
|
+
, struct = {}
|
|
265
|
+
, items = this.stack.slice(mark)
|
|
266
|
+
, i = 0
|
|
267
|
+
|
|
268
|
+
for (; i < items.length; i += 2) {
|
|
269
|
+
struct[items[i]] = items[i + 1]
|
|
270
|
+
}
|
|
271
|
+
this.stack.splice(mark, this.stack.length - mark, struct)
|
|
272
|
+
this.value = false
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
Deserializer.prototype.endBase64 = function(data) {
|
|
276
|
+
var buffer = new Buffer(data, 'base64')
|
|
277
|
+
this.push(buffer)
|
|
278
|
+
this.value = false
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Deserializer.prototype.endDateTime = function(data) {
|
|
282
|
+
var date = dateFormatter.decodeIso8601(data)
|
|
283
|
+
this.push(date)
|
|
284
|
+
this.value = false
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
var isInteger = /^-?\d+$/
|
|
288
|
+
Deserializer.prototype.endI8 = function(data) {
|
|
289
|
+
if (!isInteger.test(data)) {
|
|
290
|
+
throw new Error('Expected integer (I8) value but got \'' + data + '\'')
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
this.endString(data)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
Deserializer.prototype.endValue = function(data) {
|
|
298
|
+
if (this.value) {
|
|
299
|
+
this.endString(data)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
Deserializer.prototype.endParams = function(data) {
|
|
304
|
+
this.responseType = 'params'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
Deserializer.prototype.endFault = function(data) {
|
|
308
|
+
this.responseType = 'fault'
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Deserializer.prototype.endMethodResponse = function(data) {
|
|
312
|
+
this.type = 'methodresponse'
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
Deserializer.prototype.endMethodName = function(data) {
|
|
316
|
+
this.methodname = data
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Deserializer.prototype.endMethodCall = function(data) {
|
|
320
|
+
this.type = 'methodcall'
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = Deserializer
|
|
324
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
var xmlBuilder = require('xmlbuilder')
|
|
2
|
+
, dateFormatter = require('./date_formatter')
|
|
3
|
+
, CustomType = require('./customtype')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates the XML for an XML-RPC method call.
|
|
7
|
+
*
|
|
8
|
+
* @param {String} method - The method name.
|
|
9
|
+
* @param {Array} params - Params to pass in the call.
|
|
10
|
+
* @param {Function} callback - function (error, xml) { ... }
|
|
11
|
+
* - {Object|null} error - Any errors that occurred while building the XML,
|
|
12
|
+
* otherwise null.
|
|
13
|
+
* - {String} xml - The method call XML.
|
|
14
|
+
*/
|
|
15
|
+
exports.serializeMethodCall = function(method, params, encoding) {
|
|
16
|
+
var params = params || []
|
|
17
|
+
|
|
18
|
+
var options = { version: '1.0', allowSurrogateChars: true }
|
|
19
|
+
|
|
20
|
+
if (encoding) {
|
|
21
|
+
options.encoding = encoding
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var xml = xmlBuilder.create('methodCall', options)
|
|
25
|
+
.ele('methodName')
|
|
26
|
+
.txt(method)
|
|
27
|
+
.up()
|
|
28
|
+
.ele('params')
|
|
29
|
+
|
|
30
|
+
params.forEach(function(param) {
|
|
31
|
+
serializeValue(param, xml.ele('param'))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Includes the <?xml ...> declaration
|
|
35
|
+
return xml.doc().toString()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates the XML for an XML-RPC method response.
|
|
40
|
+
*
|
|
41
|
+
* @param {mixed} value - The value to pass in the response.
|
|
42
|
+
* @param {Function} callback - function (error, xml) { ... }
|
|
43
|
+
* - {Object|null} error - Any errors that occurred while building the XML,
|
|
44
|
+
* otherwise null.
|
|
45
|
+
* - {String} xml - The method response XML.
|
|
46
|
+
*/
|
|
47
|
+
exports.serializeMethodResponse = function(result) {
|
|
48
|
+
var xml = xmlBuilder.create('methodResponse', { version: '1.0', allowSurrogateChars: true })
|
|
49
|
+
.ele('params')
|
|
50
|
+
.ele('param')
|
|
51
|
+
|
|
52
|
+
serializeValue(result, xml)
|
|
53
|
+
|
|
54
|
+
// Includes the <?xml ...> declaration
|
|
55
|
+
return xml.doc().toString()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.serializeFault = function(fault) {
|
|
59
|
+
var xml = xmlBuilder.create('methodResponse', { version: '1.0', allowSurrogateChars: true })
|
|
60
|
+
.ele('fault')
|
|
61
|
+
|
|
62
|
+
serializeValue(fault, xml)
|
|
63
|
+
|
|
64
|
+
// Includes the <?xml ...> declaration
|
|
65
|
+
return xml.doc().toString()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function serializeValue(value, xml) {
|
|
69
|
+
var stack = [ { value: value, xml: xml } ]
|
|
70
|
+
, current = null
|
|
71
|
+
, valueNode = null
|
|
72
|
+
, next = null
|
|
73
|
+
|
|
74
|
+
while (stack.length > 0) {
|
|
75
|
+
current = stack[stack.length - 1]
|
|
76
|
+
|
|
77
|
+
if (current.index !== undefined) {
|
|
78
|
+
// Iterating a compound
|
|
79
|
+
next = getNextItemsFrame(current)
|
|
80
|
+
if (next) {
|
|
81
|
+
stack.push(next)
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
stack.pop()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// we're about to add a new value (compound or simple)
|
|
89
|
+
valueNode = current.xml.ele('value')
|
|
90
|
+
switch(typeof current.value) {
|
|
91
|
+
case 'boolean':
|
|
92
|
+
appendBoolean(current.value, valueNode)
|
|
93
|
+
stack.pop()
|
|
94
|
+
break
|
|
95
|
+
case 'string':
|
|
96
|
+
appendString(current.value, valueNode)
|
|
97
|
+
stack.pop()
|
|
98
|
+
break
|
|
99
|
+
case 'number':
|
|
100
|
+
appendNumber(current.value, valueNode)
|
|
101
|
+
stack.pop()
|
|
102
|
+
break
|
|
103
|
+
case 'object':
|
|
104
|
+
if (current.value === null) {
|
|
105
|
+
valueNode.ele('nil')
|
|
106
|
+
stack.pop()
|
|
107
|
+
}
|
|
108
|
+
else if (current.value instanceof Date) {
|
|
109
|
+
appendDatetime(current.value, valueNode)
|
|
110
|
+
stack.pop()
|
|
111
|
+
}
|
|
112
|
+
else if (Buffer.isBuffer(current.value)) {
|
|
113
|
+
appendBuffer(current.value, valueNode)
|
|
114
|
+
stack.pop()
|
|
115
|
+
}
|
|
116
|
+
else if (current.value instanceof CustomType) {
|
|
117
|
+
current.value.serialize(valueNode)
|
|
118
|
+
stack.pop()
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
if (Array.isArray(current.value)) {
|
|
122
|
+
current.xml = valueNode.ele('array').ele('data')
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
current.xml = valueNode.ele('struct')
|
|
126
|
+
current.keys = Object.keys(current.value)
|
|
127
|
+
}
|
|
128
|
+
current.index = 0
|
|
129
|
+
next = getNextItemsFrame(current)
|
|
130
|
+
if (next) {
|
|
131
|
+
stack.push(next)
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
stack.pop()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
break
|
|
138
|
+
default:
|
|
139
|
+
stack.pop()
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getNextItemsFrame(frame) {
|
|
147
|
+
var nextFrame = null
|
|
148
|
+
|
|
149
|
+
if (frame.keys) {
|
|
150
|
+
if (frame.index < frame.keys.length) {
|
|
151
|
+
var key = frame.keys[frame.index++]
|
|
152
|
+
, member = frame.xml.ele('member').ele('name').text(key).up()
|
|
153
|
+
nextFrame = {
|
|
154
|
+
value: frame.value[key]
|
|
155
|
+
, xml: member
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (frame.index < frame.value.length) {
|
|
160
|
+
nextFrame = {
|
|
161
|
+
value: frame.value[frame.index]
|
|
162
|
+
, xml: frame.xml
|
|
163
|
+
}
|
|
164
|
+
frame.index++
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return nextFrame
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function appendBoolean(value, xml) {
|
|
171
|
+
xml.ele('boolean').txt(value ? 1 : 0)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
var illegalChars = /^(?![^<&]*]]>[^<&]*)[^<&]*$/
|
|
175
|
+
function appendString(value, xml) {
|
|
176
|
+
if (value.length === 0) {
|
|
177
|
+
xml.ele('string')
|
|
178
|
+
}
|
|
179
|
+
else if (!illegalChars.test(value)) {
|
|
180
|
+
xml.ele('string').d(value)
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
xml.ele('string').txt(value)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function appendNumber(value, xml) {
|
|
188
|
+
if (value % 1 == 0) {
|
|
189
|
+
xml.ele('int').txt(value)
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
xml.ele('double').txt(value)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function appendDatetime(value, xml) {
|
|
197
|
+
xml.ele('dateTime.iso8601').txt(dateFormatter.encodeIso8601(value))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function appendBuffer(value, xml) {
|
|
201
|
+
xml.ele('base64').txt(value.toString('base64'))
|
|
202
|
+
}
|