drachtio-srf 4.4.61 → 4.4.64

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.
package/lib/dialog.js CHANGED
@@ -4,7 +4,7 @@ const only = require('only') ;
4
4
  const methods = require('sip-methods') ;
5
5
  const debug = require('debug')('drachtio:srf') ;
6
6
  const SipError = require('./sip_error');
7
- const { parseUri } = require('drachtio-sip').parser ;
7
+ const { parseUri } = require('./sip-parser/parser');
8
8
  const sdpTransform = require('sdp-transform');
9
9
 
10
10
 
@@ -12,7 +12,7 @@ const sdpTransform = require('sdp-transform');
12
12
  * Class representing a SIP Dialog.
13
13
  *
14
14
  * Note that instances of this class are not created directly by your code;
15
- * rather they are returned from the {@link Srf#createUAC}, {@link Srf#createUAC}, and {@link Srf#createB2BUA}
15
+ * rather they are returned from the {@link Srf#createUAC}, {@link Srf#createUAS}, and {@link Srf#createB2BUA}
16
16
  * @class
17
17
  * @extends EventEmitter
18
18
  */
@@ -34,6 +34,7 @@ class Dialog extends Emitter {
34
34
  this.type = type ;
35
35
  this.req = opts.req ;
36
36
  this.res = opts.res ;
37
+ this.auth = opts.auth;
37
38
  this.agent = this.res.agent ;
38
39
  this.onHold = false ;
39
40
  this.connected = true ;
@@ -171,7 +172,12 @@ class Dialog extends Emitter {
171
172
  * or NOTIFY (in the case of SUBSCRIBE)
172
173
  * @param {Object} [opts] configuration options
173
174
  * @param {Object} [opts.headers] SIP headers to add to the outgoing BYE or NOTIFY
174
- * @param {function} [callback] if provided, callback with signature <code>(err, msg)</code>
175
+ * @param {Object|Function} [opts.auth] sip credentials to use if challenged,
176
+ * or a function invoked with (req, res) and returning (err, username, password) where req is the
177
+ * request that was sent and res is the response that included the digest challenge
178
+ * @param {string} [opts.auth.username] sip username
179
+ * @param {string} [opts.auth.password] sip password
180
+ * @param {function} [callback] if provided, callback with signature <code>(err, msg)</code>
175
181
  * that provides the BYE or NOTIFY message that was sent to terminate the dialog
176
182
  * @return {Promise|Dialog} if no callback is supplied, otherwise a reference to the Dialog
177
183
  */
@@ -183,18 +189,29 @@ class Dialog extends Emitter {
183
189
  }
184
190
  this.queuedRequests = [];
185
191
 
192
+ const removeDialog = (err, res, callback) => {
193
+ this.connected = false ;
194
+ this.srf.removeDialog(this) ;
195
+ callback(err, res) ;
196
+ this.removeAllListeners();
197
+ };
198
+
186
199
  const __x = (callback) => {
187
200
  if (this.dialogType === 'INVITE') {
188
201
  this.agent.request({
189
202
  method: 'BYE',
190
203
  headers: opts.headers || {},
191
204
  stackDialogId: this.id,
205
+ auth: opts.auth || this.auth,
192
206
  _socket: this.socket
193
207
  }, (err, bye) => {
194
- this.connected = false ;
195
- this.srf.removeDialog(this) ;
196
- callback(err, bye) ;
197
- this.removeAllListeners();
208
+ if (err || !bye) {
209
+ return removeDialog(err, bye, callback);
210
+ }
211
+
212
+ bye.on('response', () => {
213
+ removeDialog(err, bye, callback);
214
+ });
198
215
  }) ;
199
216
  if (this._emitter) {
200
217
  Object.assign(this._state, {state: 'terminated'});
@@ -212,10 +229,7 @@ class Dialog extends Emitter {
212
229
  stackDialogId: this.id,
213
230
  _socket: this.socket
214
231
  }, (err, notify) => {
215
- this.connected = false ;
216
- this.srf.removeDialog(this) ;
217
- callback(err, notify) ;
218
- this.removeAllListeners();
232
+ removeDialog(err, notify, callback);
219
233
  }) ;
220
234
  }
221
235
  };
@@ -309,7 +323,7 @@ class Dialog extends Emitter {
309
323
  stackDialogId: this.id,
310
324
  body: this.local.sdp,
311
325
  _socket: this.socket,
312
- headers:
326
+ headers:
313
327
  opts.headers ? opts.headers : {'Contact': this.local.contact}
314
328
  }, (err, req) => {
315
329
  if (err) {
@@ -367,6 +381,11 @@ class Dialog extends Emitter {
367
381
  * @param {string} opts.method - SIP method to use for the request
368
382
  * @param {Object} [opts.headers] - SIP headers to apply to the request
369
383
  * @param {string} [opts.body] - body of the SIP request
384
+ * @param {Object|Function} [opts.auth] sip credentials to use if challenged,
385
+ * or a function invoked with (req, res) and returning (err, username, password) where req is the
386
+ * request that was sent and res is the response that included the digest challenge
387
+ * @param {string} [opts.auth.username] sip username
388
+ * @param {string} [opts.auth.password] sip password
370
389
  * @param {function} [callback] - callback invoked with signature <code>(err, req)</code>
371
390
  * when operation has completed
372
391
  * @return {Promise|Dialog} if no callback is supplied a Promise that resolves to the response received,
@@ -383,7 +402,7 @@ class Dialog extends Emitter {
383
402
  method: method,
384
403
  stackDialogId: this.id,
385
404
  headers: opts.headers || {},
386
- auth: opts.auth,
405
+ auth: opts.auth || this.auth,
387
406
  _socket: this.socket,
388
407
  body: opts.body
389
408
  }, (err, req) => {
@@ -2,7 +2,7 @@ const Emitter = require('events');
2
2
  const debug = require('debug')('drachtio:agent');
3
3
  const debugSocket = require('debug')('drachtio:socket');
4
4
  const WireProtocol = require('./wire-protocol') ;
5
- const SipMessage = require('drachtio-sip').SipMessage ;
5
+ const SipMessage = require('./sip-parser/message');
6
6
  const Request = require('./request') ;
7
7
  const Response = require('./response') ;
8
8
  const DigestClient = require('./digest-client') ;
@@ -658,6 +658,10 @@ class DrachtioAgent extends Emitter {
658
658
  this.pendingSipAuthTxnIdUpdate.set(res.req.stackTxnId, {});
659
659
  const client = new DigestClient(res) ;
660
660
  client.authenticate((err, req) => {
661
+ if (!req) {
662
+ sr.req.emit('response', res, ack);
663
+ return;
664
+ }
661
665
  // move all listeners from the old request to the new one we just generated
662
666
  res.req.listeners('response').forEach((l) => { req.on('response', l) ; }) ;
663
667
  res.req.emit('authenticate', req) ;
package/lib/request.js CHANGED
@@ -1,9 +1,9 @@
1
1
  const Emitter = require('events').EventEmitter ;
2
- const sip = require('drachtio-sip') ;
3
2
  const delegate = require('delegates') ;
4
3
  const assert = require('assert') ;
5
4
  const noop = require('node-noop').noop;
6
5
  const debug = require('debug')('drachtio:request');
6
+ const SipMessage = require('./sip-parser/message');
7
7
 
8
8
  class Request extends Emitter {
9
9
 
@@ -11,7 +11,7 @@ class Request extends Emitter {
11
11
  super() ;
12
12
 
13
13
  if (msg) {
14
- assert(msg instanceof sip.SipMessage) ;
14
+ assert(msg instanceof SipMessage) ;
15
15
  this.msg = msg ;
16
16
  this.meta = meta ;
17
17
  }
@@ -148,7 +148,7 @@ class Request extends Emitter {
148
148
  //add a new response to the array
149
149
  const address = meta.address ;
150
150
  const port = +meta.port;
151
- const msg = new sip.SipMessage(rawMsg) ;
151
+ const msg = new SipMessage(rawMsg) ;
152
152
  const obj = {
153
153
  time: meta.time,
154
154
  status: msg.status,
package/lib/response.js CHANGED
@@ -1,17 +1,17 @@
1
1
  const Emitter = require('events').EventEmitter ;
2
- const sip = require('drachtio-sip') ;
3
2
  const delegate = require('delegates') ;
4
3
  const status_codes = require('sip-status') ;
5
4
  const only = require('only') ;
6
5
  const noop = require('node-noop').noop;
7
6
  const assert = require('assert') ;
8
7
  const debug = require('debug')('drachtio:response');
8
+ const SipMessage = require('./sip-parser/message') ;
9
9
  class Response extends Emitter {
10
10
 
11
11
  constructor(agent) {
12
12
  super() ;
13
13
  this._agent = agent ;
14
- this.msg = new sip.SipMessage() ;
14
+ this.msg = new SipMessage() ;
15
15
  this.finished = false ;
16
16
  }
17
17
 
@@ -0,0 +1,103 @@
1
+ const only = require('only') ;
2
+ const parser = require('./parser') ;
3
+
4
+ class SipMessage {
5
+ constructor(msg) {
6
+
7
+ this.headers = {};
8
+
9
+ if (msg) {
10
+ if (typeof msg === 'string') {
11
+ this.raw = msg ;
12
+ const obj = parser.parseSipMessage(msg, true) ;
13
+ if (!obj) throw new Error('failed to parse sip message');
14
+ msg = obj;
15
+ }
16
+ Object.assign(this.headers, msg.headers || {});
17
+ Object.assign(this, only(msg, 'body method version status reason uri payload'));
18
+ }
19
+ }
20
+
21
+ get type() {
22
+ if (this.method) return 'request' ;
23
+ else if (this.status) return 'response' ;
24
+ }
25
+
26
+ get calledNumber() {
27
+ const user = this.uri.match(/sips?:(.*?)@/) ;
28
+ if (user && user.length > 1) {
29
+ return user[1].split(';')[0] ;
30
+ }
31
+ return '' ;
32
+ }
33
+
34
+ get callingNumber() {
35
+ const header = this.has('p-asserted-identity') ? this.get('p-asserted-identity') : this.get('from') ;
36
+ const user = header.match(/sips?:(.*?)@/) ;
37
+ if (user && user.length > 1) {
38
+ return user[1].split(';')[0] ;
39
+ }
40
+ return '' ;
41
+ }
42
+
43
+ get callingName() {
44
+ const header = this.has('p-asserted-identity') ? this.get('p-asserted-identity') : this.get('from') ;
45
+ const user = header.match(/^\"(.+)\"\s*<sips?:.+@/);
46
+ if (user && user.length > 1) {
47
+ return user[1];
48
+ }
49
+ return '';
50
+ }
51
+
52
+ get canFormDialog() {
53
+ return ('INVITE' === this.method || 'SUBSCRIBE' === this.method) && !this.get('to').tag ;
54
+ }
55
+
56
+ set(hdr, value) {
57
+ const hdrs = {} ;
58
+ if (typeof hdr === 'string') hdrs[hdr] = value ;
59
+ else {
60
+ Object.assign(hdrs, hdr);
61
+ }
62
+
63
+ Object.keys(hdrs).forEach((key) => {
64
+ const name = parser.getHeaderName(key) ;
65
+ const newValue = hdrs[key] ;
66
+ let v = '' ;
67
+ if (name in this.headers) {
68
+ v += this.headers[name] ;
69
+ v += ',' ;
70
+ }
71
+ v += newValue ;
72
+ this.headers[name] = v;
73
+ });
74
+
75
+ return this ;
76
+ }
77
+
78
+ get(hdr) {
79
+ if (this.has(hdr)) { return this.headers[parser.getHeaderName(hdr)] ; }
80
+ }
81
+
82
+ has(hdr) {
83
+ const name = parser.getHeaderName(hdr) ;
84
+ return name in this.headers ;
85
+ }
86
+
87
+ getParsedHeader(hdr) {
88
+ const name = parser.getHeaderName(hdr) ;
89
+ const v = this.headers[name] ;
90
+ const fn = parser.getParser(hdr.toLowerCase()) ;
91
+ return fn({s:v, i:0}) ;
92
+ }
93
+
94
+ toString() {
95
+ return parser.stringifySipMessage(this) ;
96
+ }
97
+
98
+ }
99
+
100
+ SipMessage.parseUri = parser.parseUri ;
101
+
102
+
103
+ exports = module.exports = SipMessage ;
@@ -0,0 +1,426 @@
1
+
2
+ exports = module.exports = {
3
+ parseSipMessage: parseMessage,
4
+ stringifySipMessage: stringify,
5
+ stringifyUri: stringifyUri,
6
+ parseUri: parseUri,
7
+ getParser: function(hdr) {
8
+ return parsers[hdr] || parseGenericHeader ;
9
+ },
10
+ getStringifier: function(hdr) {
11
+ return stringifiers[hdr];
12
+ },
13
+ getHeaderName: getHeaderName
14
+ } ;
15
+
16
+ const headerNames = {
17
+ 'call-id': 'Call-ID',
18
+ 'content-length': 'Content-Length'
19
+ } ;
20
+
21
+ const customHeaderNames = [
22
+ 'Diversion'
23
+ ] ;
24
+
25
+ function getHeaderName(hdr) {
26
+ if (0 === hdr.indexOf('X-') ||
27
+ (0 === hdr.indexOf('P-') && 0 !== hdr.indexOf('P-Asserted')) ||
28
+ -1 !== customHeaderNames.indexOf(hdr)) return hdr ;
29
+ const name = unescape(hdr).toLowerCase();
30
+ return compactForm[name] || name;
31
+ }
32
+
33
+ function parseResponse(rs, m) {
34
+ const r = rs.match(/^SIP\/(\d+\.\d+)\s+(\d+)\s*(.*)\s*$/);
35
+
36
+ if (r) {
37
+ m.version = r[1];
38
+ m.status = +r[2];
39
+ m.reason = r[3];
40
+
41
+ return m;
42
+ }
43
+ }
44
+
45
+ function parseRequest(rq, m) {
46
+ const r = rq.match(/^([\w\-.!%*_+`'~]+)\s([^\s]+)\sSIP\s*\/\s*(\d+\.\d+)/);
47
+
48
+ if (r) {
49
+ m.method = unescape(r[1]);
50
+ m.uri = r[2];
51
+ m.version = r[3];
52
+
53
+ return m;
54
+ }
55
+ }
56
+
57
+ function applyRegex(regex, data) {
58
+ regex.lastIndex = data.i;
59
+ const r = regex.exec(data.s);
60
+
61
+ if (r && (r.index === data.i)) {
62
+ data.i = regex.lastIndex;
63
+ return r;
64
+ }
65
+ }
66
+
67
+ function parseParams(data, hdr) {
68
+ hdr.params = hdr.params || {};
69
+
70
+ const re = /\s*;\s*([\w\-.!%*_+`'~]+)(?:\s*=\s*([\w\-.!%*_+`'~]+|"[^"\\]*(\\.[^"\\]*)*"))?/g;
71
+
72
+ for (let r = applyRegex(re, data); r; r = applyRegex(re, data)) {
73
+ hdr.params[r[1].toLowerCase()] = r[2];
74
+ }
75
+
76
+ return hdr;
77
+ }
78
+
79
+ function parseMultiHeader(parser, d, h) {
80
+ h = h || [];
81
+
82
+ const re = /\s*,\s*/g;
83
+ do {
84
+ h.push(parser(d));
85
+ } while (d.i < d.s.length && applyRegex(re, d));
86
+
87
+ return h;
88
+ }
89
+
90
+ function parseGenericHeader(d, h) {
91
+ return h ? h + ',' + d.s : d.s;
92
+ }
93
+
94
+ function parseAOR(data) {
95
+ // eslint-disable-next-line max-len
96
+ const r = applyRegex(/((?:[\w\-.!%*_+`'~]+)(?:\s+[\w\-.!%*_+`'~]+)*|"[^"\\]*(?:\\.[^"\\]*)*")?\s*\<\s*([^>]*)\s*\>|((?:[^\s@"<]@)?[^\s;]+)/g, data);
97
+ return parseParams(data, {name: r[1], uri: r[2] || r[3] || ''});
98
+ }
99
+
100
+ function parseAorWithUri(data) {
101
+ const r = parseAOR(data);
102
+ r.uri = parseUri(r.uri);
103
+ return r;
104
+ }
105
+
106
+ function parseVia(data) {
107
+ const r = applyRegex(/SIP\s*\/\s*(\d+\.\d+)\s*\/\s*([\S]+)\s+([^\s;:]+)(?:\s*:\s*(\d+))?/g, data);
108
+ return parseParams(data, {version: r[1], protocol: r[2], host: r[3], port: r[4] && +r[4]});
109
+ }
110
+
111
+ function parseCSeq(d) {
112
+ const r = /(\d+)\s*([\S]+)/.exec(d.s);
113
+ return { seq: +r[1], method: unescape(r[2]) };
114
+ }
115
+
116
+ function parseAuthHeader(d) {
117
+ const r1 = applyRegex(/([^\s]*)\s+/g, d);
118
+ const a = {scheme: r1[1]};
119
+
120
+ let r2 = applyRegex(/([^\s,"=]*)\s*=\s*([^\s,"]+|"[^"\\]*(?:\\.[^"\\]*)*")\s*/g, d);
121
+ a[r2[1]] = r2[2];
122
+
123
+ while (r2 = applyRegex(/,\s*([^\s,"=]*)\s*=\s*([^\s,"]+|"[^"\\]*(?:\\.[^"\\]*)*")\s*/g, d)) {
124
+ a[r2[1]] = r2[2];
125
+ }
126
+
127
+ return a;
128
+ }
129
+
130
+ function parseAuthenticationInfoHeader(d) {
131
+ const a = {};
132
+ let r = applyRegex(/([^\s,"=]*)\s*=\s*([^\s,"]+|"[^"\\]*(?:\\.[^"\\]*)*")\s*/g, d);
133
+ a[r[1]] = r[2];
134
+
135
+ while (r = applyRegex(/,\s*([^\s,"=]*)\s*=\s*([^\s,"]+|"[^"\\]*(?:\\.[^"\\]*)*")\s*/g, d)) {
136
+ a[r[1]] = r[2];
137
+ }
138
+ return a;
139
+ }
140
+
141
+ const compactForm = {
142
+ i: 'call-id',
143
+ m: 'contact',
144
+ e: 'contact-encoding',
145
+ l: 'content-length',
146
+ c: 'content-type',
147
+ f: 'from',
148
+ s: 'subject',
149
+ k: 'supported',
150
+ t: 'to',
151
+ v: 'via'
152
+ };
153
+
154
+ const parsers = {
155
+ 'to': parseAOR,
156
+ 'from': parseAOR,
157
+ 'contact': function(v, h) {
158
+ if (v == '*')
159
+ return v;
160
+ else
161
+ return parseMultiHeader(parseAOR, v, h);
162
+ },
163
+ 'route': parseMultiHeader.bind(0, parseAorWithUri),
164
+ 'record-route': parseMultiHeader.bind(0, parseAorWithUri),
165
+ 'path': parseMultiHeader.bind(0, parseAorWithUri),
166
+ 'cseq': parseCSeq,
167
+ 'content-length': function(v) { return +v.s; },
168
+ 'via': parseMultiHeader.bind(0, parseVia),
169
+ 'www-authenticate': parseMultiHeader.bind(0, parseAuthHeader),
170
+ 'proxy-authenticate': parseMultiHeader.bind(0, parseAuthHeader),
171
+ 'authorization': parseMultiHeader.bind(0, parseAuthHeader),
172
+ 'proxy-authorization': parseMultiHeader.bind(0, parseAuthHeader),
173
+ 'authentication-info': parseAuthenticationInfoHeader,
174
+ 'refer-to': parseAOR,
175
+ 'referred-by': parseAOR,
176
+ 'p-asserted-identity': parseAOR,
177
+ 'remote-party-id': parseAOR
178
+ };
179
+
180
+
181
+ function parse(data, lazy) {
182
+ data = data.split(/\r\n(?![ \t])/);
183
+
184
+ if (data[0] === '')
185
+ return;
186
+
187
+ const m = {};
188
+
189
+ if (!(parseResponse(data[0], m) || parseRequest(data[0], m)))
190
+ return;
191
+
192
+ m.headers = {};
193
+
194
+ for (let i = 1; i < data.length; ++i) {
195
+ const r = data[i].match(/^([\S]*?)\s*:\s*([\s\S]*)$/);
196
+ if (!r) {
197
+ return;
198
+ }
199
+
200
+ const name = getHeaderName(r[1]) ;
201
+ const fnParse = parsers[name] || parseGenericHeader ;
202
+
203
+ if (lazy === true) {
204
+ let v = '' ;
205
+ if (name in m.headers) {
206
+ v += m.headers[name] ;
207
+ v += ',' ;
208
+ }
209
+ v += r[2] ;
210
+ m.headers[name] = v ;
211
+ }
212
+ else {
213
+ m.headers[name] = fnParse({s:r[2], i:0}, m.headers[name]) ;
214
+ }
215
+ }
216
+
217
+ return m;
218
+ }
219
+
220
+ function parseUri(s) {
221
+ if (typeof s === 'object')
222
+ return s;
223
+
224
+ // eslint-disable-next-line max-len
225
+ //const re = /^(sips?):(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-\.]+)(?::(\d+))?((?:;[^\s=\?>;]+(?:=[^\s?\;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/;
226
+ // eslint-disable-next-line max-len
227
+ const re = /^(sips?):(?:([^\s>:@]+)(?::([^\s@>]+))?@)?(?:(|(?:\[.*\])|(?:[0-9A-Za-z\-_]+\.)+[0-9A-Za-z\-_]+)|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(?::(\d+))?((?:;[^\s=\?>;]+(?:=[^\s?\;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/;
228
+
229
+ const r = re.exec(s);
230
+
231
+ if (r) {
232
+ return {
233
+ family: /\[.*\]/.test(r[4]) ? 'ipv6' : 'ipv4',
234
+ scheme: r[1],
235
+ user: r[2],
236
+ password: r[3],
237
+ host: r[4],
238
+ port: +r[5],
239
+ params: (r[6].match(/([^;=]+)(=([^;=]+))?/g) || [])
240
+ .map(function(s) { return s.split('='); })
241
+ .reduce(function(params, x) { params[x[0]] = x[1] || null; return params;}, {}),
242
+ headers: ((r[7] || '').match(/[^&=]+=[^&=]+/g) || [])
243
+ .map(function(s) { return s.split('='); })
244
+ .reduce(function(params, x) { params[x[0]] = x[1]; return params; }, {})
245
+ } ;
246
+ }
247
+ }
248
+
249
+ function stringifyVersion(v) {
250
+ return v || '2.0';
251
+ }
252
+
253
+ function stringifyParams(params) {
254
+ let s = '';
255
+ for (const n in params) {
256
+ s += ';' + n + (params[n] ? '=' + params[n] : '');
257
+ }
258
+
259
+ return s;
260
+ }
261
+
262
+ function stringifyUri(uri) {
263
+ if (typeof uri === 'string')
264
+ return uri;
265
+
266
+ let s = (uri.scheme || 'sip') + ':';
267
+
268
+ if (uri.user) {
269
+ if (uri.password)
270
+ s += uri.user + ':' + uri.password + '@';
271
+ else
272
+ s += uri.user + '@';
273
+ }
274
+
275
+ s += uri.host;
276
+
277
+ if (uri.port)
278
+ s += ':' + uri.port;
279
+
280
+ if (uri.params)
281
+ s += stringifyParams(uri.params);
282
+
283
+ if (uri.headers) {
284
+ const h = Object.keys(uri.headers).map(function(x) { return x + '=' + uri.headers[x];}).join('&');
285
+ if (h.length) s += '?' + h;
286
+ }
287
+ return s;
288
+ }
289
+
290
+ function stringifyAOR(aor) {
291
+ return (aor.name || '') + ' <' + stringifyUri(aor.uri) + '>' + stringifyParams(aor.params);
292
+ }
293
+
294
+ function stringifyAuthHeader(a) {
295
+ const s = [];
296
+
297
+ for (const n in a) {
298
+ if (n !== 'scheme' && a[n] !== undefined) {
299
+ s.push(n + '=' + a[n]);
300
+ }
301
+ }
302
+
303
+ return a.scheme ? a.scheme + ' ' + s.join(',') : s.join(',');
304
+ }
305
+
306
+ exports.stringifyAuthHeader = stringifyAuthHeader;
307
+
308
+ const stringifiers = {
309
+ via: function(h) {
310
+ return h.map(function(via) {
311
+ return 'Via: SIP/' + stringifyVersion(via.version) + '/' + via.protocol.toUpperCase() + ' ' +
312
+ via.host + (via.port ? ':' + via.port : '') + stringifyParams(via.params) + '\r\n';
313
+ }).join('');
314
+ },
315
+ to: function(h) {
316
+ return 'To: ' + stringifyAOR(h) + '\r\n';
317
+ },
318
+ from: function(h) {
319
+ return 'From: ' + stringifyAOR(h) + '\r\n';
320
+ },
321
+ contact: function(h) {
322
+ return 'Contact: ' + ((h !== '*' && h.length) ? h.map(stringifyAOR).join(', ') : '*') + '\r\n';
323
+ },
324
+ route: function(h) {
325
+ return h.length ? 'Route: ' + h.map(stringifyAOR).join(', ') + '\r\n' : '';
326
+ },
327
+ 'record-route': function(h) {
328
+ return h.length ? 'Record-Route: ' + h.map(stringifyAOR).join(', ') + '\r\n' : '';
329
+ },
330
+ 'path': function(h) {
331
+ return h.length ? 'Path: ' + h.map(stringifyAOR).join(', ') + '\r\n' : '';
332
+ },
333
+ cseq: function(cseq) {
334
+ return 'CSeq: ' + cseq.seq + ' ' + cseq.method + '\r\n';
335
+ },
336
+ 'www-authenticate': function(h) {
337
+ return h.map(function(x) { return 'WWW-Authenticate: ' + stringifyAuthHeader(x) + '\r\n'; }).join('');
338
+ },
339
+ 'proxy-authenticate': function(h) {
340
+ return h.map(function(x) { return 'Proxy-Authenticate: ' + stringifyAuthHeader(x) + '\r\n'; }).join('');
341
+ },
342
+ 'authorization': function(h) {
343
+ return h.map(function(x) { return 'Authorization: ' + stringifyAuthHeader(x) + '\r\n'; }).join('');
344
+ },
345
+ 'proxy-authorization': function(h) {
346
+ return h.map(function(x) { return 'Proxy-Authorization: ' + stringifyAuthHeader(x) + '\r\n'; }).join('');
347
+ },
348
+ 'authentication-info': function(h) {
349
+ return 'Authentication-Info: ' + stringifyAuthHeader(h) + '\r\n';
350
+ },
351
+ 'refer-to': function(h) { return 'Refer-To: ' + stringifyAOR(h) + '\r\n'; }
352
+ };
353
+
354
+ function stringify(m) {
355
+ let s;
356
+ if (m.status) {
357
+ s = 'SIP/' + stringifyVersion(m.version) + ' ' + m.status + ' ' + m.reason + '\r\n';
358
+ }
359
+ else {
360
+ s = m.method + ' ' + stringifyUri(m.uri) + ' SIP/' + stringifyVersion(m.version) + '\r\n';
361
+ }
362
+
363
+ m.headers['content-length'] = (m.body || '').length;
364
+
365
+ for (const n in m.headers) {
366
+ if (typeof m.headers[n] === 'string' || !stringifiers[n])
367
+ s += (headerNames[n] || n) + ': ' + m.headers[n] + '\r\n';
368
+ else
369
+ s += stringifiers[n](m.headers[n].parsed, n);
370
+ }
371
+
372
+ s += '\r\n';
373
+
374
+ if (m.body)
375
+ s += m.body;
376
+
377
+ return s;
378
+ }
379
+
380
+
381
+ function parseMessage(s, lazy) {
382
+ const r = s.toString('utf8').split('\r\n\r\n');
383
+ if (r) {
384
+ const m = parse(r[0], lazy);
385
+ if (m) {
386
+ r.shift() ;
387
+ if (m.headers['content-length']) {
388
+ const body = 1 === r.length ? r[0] : r.join('\r\n\r\n') ;
389
+ const c = Math.max(0, Math.min(m.headers['content-length'], Buffer.byteLength(body, 'utf8')));
390
+ m.body = body.substring(0, c);
391
+ }
392
+ else {
393
+ m.body = r[0];
394
+ }
395
+ m.payload = [] ;
396
+
397
+ let arr ;
398
+ if (r.length > 1 &&
399
+ m.headers['content-type'] &&
400
+ -1 !== m.headers['content-type'].indexOf('multipart/') &&
401
+ (arr = /.*;\s*boundary=\"?(.*?)\"?$/.exec(m.headers['content-type']))) {
402
+
403
+ const segments = m.body.split('--' + arr[1] + '\r\n');
404
+ if (0 === segments[0].length) {
405
+ segments.shift() ;
406
+ }
407
+ for (let i = 0; i < segments.length; i++) {
408
+ const ct = /Content-Type:\s*(.*)\r\n/.exec(segments[i]) ;
409
+ const stanzas = segments[i].split('\r\n\r\n') ;
410
+ m.payload.push({
411
+ type: ct ? ct[1] : null,
412
+ content: stanzas[1]
413
+ });
414
+ }
415
+ }
416
+ else {
417
+ m.payload.push({
418
+ type: m.headers['content-type'],
419
+ content: m.body
420
+ }) ;
421
+ }
422
+
423
+ return m;
424
+ }
425
+ }
426
+ }
package/lib/srf.js CHANGED
@@ -3,7 +3,7 @@ const Dialog = require('./dialog') ;
3
3
  const assert = require('assert') ;
4
4
  const Emitter = require('events') ;
5
5
  const delegate = require('delegates') ;
6
- const parser = require('drachtio-sip').parser ;
6
+ const parser = require('./sip-parser/parser');
7
7
  const methods = require('sip-methods') ;
8
8
  const SipError = require('./sip_error') ;
9
9
  const async = require('async') ;
@@ -557,7 +557,7 @@ class Srf extends Emitter {
557
557
  return new Promise((resolve) => {
558
558
  ack({body: localSdp}) ;
559
559
 
560
- const dialog = new Dialog(this, 'uac', {req: req, res: res}) ;
560
+ const dialog = new Dialog(this, 'uac', {req: req, res: res, auth: opts.auth}) ;
561
561
  dialog.local.sdp = localSdp ;
562
562
  this.addDialog(dialog) ;
563
563
  resolve(dialog) ;
@@ -582,7 +582,7 @@ class Srf extends Emitter {
582
582
 
583
583
  if ((200 === res.status && method === 'INVITE') ||
584
584
  ((202 === res.status || 200 === res.status) && method === 'SUBSCRIBE')) {
585
- const dialog = new Dialog(this, 'uac', {req: req, res: res}) ;
585
+ const dialog = new Dialog(this, 'uac', {req: req, res: res, auth: opts.auth}) ;
586
586
  if (req._dialogState) {
587
587
  dialog.stateEmitter = {
588
588
  emitter: opts.dialogStateEmitter,
@@ -1670,7 +1670,7 @@ class Srf extends Emitter {
1670
1670
  }
1671
1671
 
1672
1672
  static get SipMessage() {
1673
- return require('drachtio-sip').SipMessage;
1673
+ return require('./sip-parser/message');
1674
1674
  }
1675
1675
 
1676
1676
  static get SipRequest() {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "drachtio-srf",
3
- "version": "4.4.61",
3
+ "version": "4.4.64",
4
4
  "description": "drachtio signaling resource framework",
5
5
  "main": "lib/srf.js",
6
6
  "scripts": {
7
7
  "test": "NODE_ENV=test node test/ ",
8
8
  "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
9
+ "unittests": "./node_modules/.bin/mocha ./test/unit-tests/",
9
10
  "jslint": "eslint lib"
10
11
  },
11
12
  "engines": {
@@ -26,7 +27,6 @@
26
27
  "debug": "^3.2.7",
27
28
  "delegates": "^0.1.0",
28
29
  "deprecate": "^1.1.1",
29
- "drachtio-sip": "^0.6.3",
30
30
  "node-noop": "0.0.1",
31
31
  "only": "0.0.2",
32
32
  "sdp-transform": "^2.14.1",
@@ -40,7 +40,10 @@
40
40
  "config": "^3.3.3",
41
41
  "eslint": "^7.18.0",
42
42
  "eslint-plugin-promise": "^4.2.1",
43
+ "mocha": "^9.2.0",
43
44
  "nyc": "^15.1.0",
45
+ "should": "^13.2.3",
46
+ "sip-message-examples": "0.0.6",
44
47
  "tape": "^5.2.2"
45
48
  }
46
49
  }
@@ -150,3 +150,14 @@ services:
150
150
  networks:
151
151
  testbed:
152
152
  ipv4_address: 172.29.0.23
153
+
154
+ sipp-uas-bye-with-auth:
155
+ image: drachtio/sipp:latest
156
+ command: sipp -sf /tmp/uas-bye-with-auth.xml
157
+ container_name: sipp-uas-bye-with-auth
158
+ volumes:
159
+ - ./scenarios:/tmp
160
+ tty: true
161
+ networks:
162
+ testbed:
163
+ ipv4_address: 172.29.0.24
package/test/index.js CHANGED
@@ -1,9 +1,21 @@
1
- require('./docker_start');
2
- require('./b2b');
3
- require('./reinvite-tests');
4
- require('./uac');
5
- require('./uas');
6
- require('./proxy');
7
- require('./utils');
8
- require('./refer');
9
- require('./docker_stop');
1
+ const exec = require('child_process').exec;
2
+
3
+ console.log('Run unit tests');
4
+
5
+ exec('"npm" run unittests', (err, stdout, stderr) => {
6
+ console.log(stdout);
7
+
8
+ console.log('Run integration tests');
9
+
10
+ require('./docker_start');
11
+ require('./b2b');
12
+ require('./reinvite-tests');
13
+ require('./uac');
14
+ require('./uas');
15
+ require('./proxy');
16
+ require('./utils');
17
+ require('./refer');
18
+ require('./docker_stop');
19
+ });
20
+
21
+
package/test/parser.js ADDED
@@ -0,0 +1,8 @@
1
+ const test = require('tape') ;
2
+ const exec = require('child_process').exec ;
3
+
4
+ test('testing parser functions', (t) => {
5
+ const Srf = require('..');
6
+ const {parseUri} = Srf;
7
+ t.ok(typeof parseUri === 'function');
8
+ });
@@ -0,0 +1,164 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1" ?>
2
+ <!DOCTYPE scenario SYSTEM "sipp.dtd">
3
+
4
+ <!-- This program is free software; you can redistribute it and/or -->
5
+ <!-- modify it under the terms of the GNU General Public License as -->
6
+ <!-- published by the Free Software Foundation; either version 2 of the -->
7
+ <!-- License, or (at your option) any later version. -->
8
+ <!-- -->
9
+ <!-- This program is distributed in the hope that it will be useful, -->
10
+ <!-- but WITHOUT ANY WARRANTY; without even the implied warranty of -->
11
+ <!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -->
12
+ <!-- GNU General Public License for more details. -->
13
+ <!-- -->
14
+ <!-- You should have received a copy of the GNU General Public License -->
15
+ <!-- along with this program; if not, write to the -->
16
+ <!-- Free Software Foundation, Inc., -->
17
+ <!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -->
18
+ <!-- -->
19
+ <!-- Sipp default 'uas' scenario. -->
20
+ <!-- -->
21
+
22
+ <scenario name="Basic UAS responder">
23
+ <!-- By adding rrs="true" (Record Route Sets), the route sets -->
24
+ <!-- are saved and used for following messages sent. Useful to test -->
25
+ <!-- against stateful SIP proxies/B2BUAs. -->
26
+ <recv request="INVITE" crlf="true">
27
+ </recv>
28
+
29
+ <send>
30
+ <![CDATA[
31
+
32
+ SIP/2.0 100 Trying
33
+ [last_Via:]
34
+ [last_From:]
35
+ [last_To:]
36
+ [last_Call-ID:]
37
+ [last_CSeq:]
38
+ Content-Length: 0
39
+
40
+ ]]>
41
+ </send>
42
+
43
+ <send retrans="500">
44
+ <![CDATA[
45
+
46
+ SIP/2.0 407 Proxy Authentication Required
47
+ [last_Via:]
48
+ [last_From:]
49
+ [last_To:];tag=[pid]SIPpTag01[call_number]
50
+ [last_Call-ID:]
51
+ [last_CSeq:]
52
+ Proxy-Authenticate: Digest realm="local", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
53
+ Content-Length: 0
54
+
55
+ ]]>
56
+ </send>
57
+
58
+ <recv request="ACK"
59
+ rtd="true"
60
+ crlf="true">
61
+ </recv>
62
+
63
+ <recv request="INVITE" crlf="true">
64
+ <action>
65
+ <ereg regexp=".*" search_in="hdr" header="Proxy-Authorization:" assign_to="1" />
66
+ </action>
67
+ </recv>
68
+
69
+ <send>
70
+ <![CDATA[
71
+
72
+ SIP/2.0 180 Ringing
73
+ [last_Via:]
74
+ [last_From:]
75
+ [last_To:];tag=[pid]SIPpTag01[call_number]
76
+ [last_Call-ID:]
77
+ [last_CSeq:]
78
+ Subject:[$1]
79
+ Content-Length: 0
80
+
81
+ ]]>
82
+ </send>
83
+
84
+ <send retrans="500">
85
+ <![CDATA[
86
+
87
+ SIP/2.0 200 OK
88
+ [last_Via:]
89
+ [last_From:]
90
+ [last_To:];tag=[pid]SIPpTag01[call_number]
91
+ [last_Call-ID:]
92
+ [last_CSeq:]
93
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
94
+ Content-Type: application/sdp
95
+ Content-Length: [len]
96
+
97
+ v=0
98
+ o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
99
+ s=-
100
+ c=IN IP[media_ip_type] [media_ip]
101
+ t=0 0
102
+ m=audio [media_port] RTP/AVP 0
103
+ a=rtpmap:0 PCMU/8000
104
+
105
+ ]]>
106
+ </send>
107
+
108
+ <recv request="ACK"
109
+ optional="true"
110
+ rtd="true"
111
+ crlf="true">
112
+ </recv>
113
+ <recv request="BYE">
114
+ </recv>
115
+
116
+ <send retrans="500">
117
+ <![CDATA[
118
+
119
+ SIP/2.0 407 Proxy Authentication Required
120
+ [last_Via:]
121
+ [last_From:]
122
+ [last_To:];tag=[pid]SIPpTag01[call_number]
123
+ [last_Call-ID:]
124
+ [last_CSeq:]
125
+ Proxy-Authenticate: Digest realm="local", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
126
+ Content-Length: 0
127
+
128
+ ]]>
129
+ </send>
130
+
131
+ <recv request="BYE">
132
+ <action>
133
+ <ereg regexp=".*" search_in="hdr" header="Proxy-Authorization:" assign_to="1" />
134
+ </action>
135
+ </recv>
136
+
137
+ <send>
138
+ <![CDATA[
139
+
140
+ SIP/2.0 200 OK
141
+ [last_Via:]
142
+ [last_From:]
143
+ [last_To:]
144
+ [last_Call-ID:]
145
+ [last_CSeq:]
146
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
147
+ Content-Length: 0
148
+
149
+ ]]>
150
+ </send>
151
+
152
+ <!-- Keep the call open for a while in case the 200 is lost to be -->
153
+ <!-- able to retransmit it if we receive the BYE again. -->
154
+ <timewait milliseconds="4000"/>
155
+
156
+
157
+ <!-- definition of the response time repartition table (unit is ms) -->
158
+ <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
159
+
160
+ <!-- definition of the call length repartition table (unit is ms) -->
161
+ <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
162
+
163
+ </scenario>
164
+
package/test/uac.js CHANGED
@@ -10,7 +10,7 @@ process.on('unhandledRejection', (reason, p) => {
10
10
  function connect(srf) {
11
11
  return new Promise((resolve, reject) => {
12
12
  srf.connect(config.get('drachtio-sut'));
13
- srf.on('connect', () => { resolve();});
13
+ srf.on('connect', () => { resolve(); });
14
14
  });
15
15
  }
16
16
 
@@ -40,7 +40,7 @@ test('UAC', (t) => {
40
40
  headers: {
41
41
  To: 'sip:dhorton@sip.drachtio.org',
42
42
  From: 'sip:dhorton@sip.drachtio.org'
43
- },
43
+ },
44
44
  followRedirects: true,
45
45
  keepUriOnRedirect: true
46
46
  });
@@ -60,7 +60,7 @@ test('UAC', (t) => {
60
60
  }
61
61
  });
62
62
  })
63
- .then((uac) => {
63
+ .then((uac) => {
64
64
  srf.disconnect();
65
65
  return t.pass('Srf#createUAC returns a Promise that resolves with the uac dialog');
66
66
  })
@@ -94,52 +94,52 @@ test('UAC', (t) => {
94
94
  })
95
95
  .then(() => {
96
96
  return srf.createUAC('sip:sipp-uas', {
97
- method: 'INVITE',
98
- callingNumber: '12345',
99
- headers: {
100
- 'Subject': 'sending Contact based on callingNumber'
101
- }
102
- });
97
+ method: 'INVITE',
98
+ callingNumber: '12345',
99
+ headers: {
100
+ 'Subject': 'sending Contact based on callingNumber'
101
+ }
102
+ });
103
103
  })
104
104
  .then((uac) => uac.destroy())
105
105
  .then(() => {
106
106
  srf.disconnect();
107
107
  return t.pass('Srf#createUAC accepts opts.callingNumber');
108
108
  })
109
-
109
+
110
110
  .then(() => {
111
111
  srf = new Srf();
112
112
  return connect(srf);
113
113
  })
114
114
  .then(() => {
115
115
  return srf.createUAC('sip:sipp-uas', {
116
- method: 'INVITE',
117
- callingNumber: '12345',
118
- headers: {
119
- 'Subject': 'sending explicit Contact',
120
- 'Contact': 'sip:foo@localhost'
121
- }
122
- });
116
+ method: 'INVITE',
117
+ callingNumber: '12345',
118
+ headers: {
119
+ 'Subject': 'sending explicit Contact',
120
+ 'Contact': 'sip:foo@localhost'
121
+ }
122
+ });
123
123
  })
124
124
  .then((uac) => uac.destroy())
125
125
  .then(() => {
126
126
  srf.disconnect();
127
127
  return t.pass('Srf#createUAC accepts opts.callingNumber');
128
128
  })
129
-
129
+
130
130
  .then(() => {
131
131
  srf = new Srf();
132
132
  return connect(srf);
133
133
  })
134
134
  .then(() => {
135
135
  return srf.createUAC('sip:sipp-uas', {
136
- method: 'INVITE',
137
- callingNumber: '12345',
138
- headers: {
139
- 'Subject': 'sending explicit contact',
140
- 'contact': 'sip:foo@localhost'
141
- }
142
- });
136
+ method: 'INVITE',
137
+ callingNumber: '12345',
138
+ headers: {
139
+ 'Subject': 'sending explicit contact',
140
+ 'contact': 'sip:foo@localhost'
141
+ }
142
+ });
143
143
  })
144
144
  .then((uac) => uac.destroy())
145
145
  .then(() => {
@@ -215,6 +215,29 @@ test('UAC', (t) => {
215
215
  srf.disconnect();
216
216
  return t.pass('Srf#createUAC can handle digest authentication');
217
217
  })
218
+ .then(() => {
219
+ srf = new Srf();
220
+ return connect(srf);
221
+ })
222
+ .then(() => {
223
+ return srf.createUAC('sip:172.29.0.24', {
224
+ method: 'INVITE',
225
+ headers: {
226
+ To: 'sip:dhorton@sip.drachtio.org',
227
+ From: 'sip:dhorton@sip.drachtio.org'
228
+ },
229
+ auth: {
230
+ username: 'foo',
231
+ password: 'bar'
232
+ }
233
+ });
234
+ })
235
+ .then((uac) => {
236
+ return uac.destroy().then(() => {
237
+ srf.disconnect();
238
+ return t.pass('Srf#createUAC can handle bye with digest authentication');
239
+ });
240
+ })
218
241
 
219
242
  .then(() => {
220
243
  srf = new Srf();
@@ -266,15 +289,15 @@ test('UAC', (t) => {
266
289
  password: 'bar'
267
290
  }
268
291
  })
269
- .then((req) => {
270
- req.on('response', (res) => {
271
- if (res.status === 200) return resolve();
272
- reject(new Error(`REGISTER was rejected after auth with ${res.status}`));
292
+ .then((req) => {
293
+ req.on('response', (res) => {
294
+ if (res.status === 200) return resolve();
295
+ reject(new Error(`REGISTER was rejected after auth with ${res.status}`));
296
+ });
297
+ })
298
+ .catch((err) => {
299
+ t.end(err, 'Srf#request returns a Promise');
273
300
  });
274
- })
275
- .catch((err) => {
276
- t.end(err, 'Srf#request returns a Promise');
277
- });
278
301
  });
279
302
  })
280
303
  .then((uac) => {
@@ -301,15 +324,15 @@ test('UAC', (t) => {
301
324
  password: 'bar'
302
325
  }
303
326
  })
304
- .then((req) => {
305
- req.on('response', (res) => {
306
- if (res.status === 200) return resolve();
307
- reject(new Error(`REGISTER was rejected after auth with ${res.status}`));
327
+ .then((req) => {
328
+ req.on('response', (res) => {
329
+ if (res.status === 200) return resolve();
330
+ reject(new Error(`REGISTER was rejected after auth with ${res.status}`));
331
+ });
332
+ })
333
+ .catch((err) => {
334
+ t.end(err, 'srf.request accepts opts.uri');
308
335
  });
309
- })
310
- .catch((err) => {
311
- t.end(err, 'srf.request accepts opts.uri');
312
- });
313
336
  });
314
337
  })
315
338
  .then((uac) => {
@@ -364,7 +387,7 @@ test('UAC', (t) => {
364
387
  cbRequest: (err, req) => inviteSent = req,
365
388
  cbProvisional: (response) => {
366
389
  if (response.status < 200) {
367
- inviteSent.cancel({headers: {'Reason': 'SIP;cause=200;text="Call completed elsewhere"'}});
390
+ inviteSent.cancel({ headers: { 'Reason': 'SIP;cause=200;text="Call completed elsewhere"' } });
368
391
  }
369
392
  }
370
393
  }, (err, dlg) => {
@@ -386,4 +409,4 @@ test('UAC', (t) => {
386
409
  t.error(err);
387
410
  });
388
411
 
389
- });
412
+ });
@@ -0,0 +1,150 @@
1
+ require('assert');
2
+ require('mocha');
3
+ require('should');
4
+
5
+ const assert = require('assert');
6
+ const examples = require('sip-message-examples');
7
+ const SipMessage = require('../../lib/sip-parser/message');
8
+ const parser = require('../../lib/sip-parser/parser');
9
+ const parseUri = parser.parseUri;
10
+ const Srf = require('../..');
11
+ assert.ok(typeof parseUri === 'function');
12
+ assert.ok(typeof Srf.parseUri === 'function');
13
+ console.log(`typeof parseUri is ${typeof parseUri}`);
14
+ console.log(`typeof Srf.parseUri is ${typeof Srf.parseUri}`);
15
+
16
+ describe('Parser', function () {
17
+ it('should provide headers as string values', function () {
18
+ var msg = new SipMessage(examples('invite'));
19
+ (typeof msg.get('from')).should.eql('string');
20
+ });
21
+ it('should optionally provide a parsed header', function () {
22
+ var msg = new SipMessage(examples('invite'));
23
+ var obj = msg.getParsedHeader('from');
24
+ obj.should.be.type('object');
25
+ obj.should.have.property('uri');
26
+ });
27
+
28
+ it('getting a header should return the same value provided to set', function () {
29
+ var msg = new SipMessage();
30
+ msg.set('From', '<sip:daveh@localhost>;tag=1234');
31
+ msg.get('From').should.eql('<sip:daveh@localhost>;tag=1234');
32
+ });
33
+ it('setting a header should be case insensitive', function () {
34
+ var msg = new SipMessage();
35
+ msg.set('from', '<sip:daveh@localhost>;tag=1234');
36
+ msg.get('From').should.eql('<sip:daveh@localhost>;tag=1234');
37
+ });
38
+ it('getting a header should be case insensitive', function () {
39
+ var msg = new SipMessage();
40
+ msg.set('From', '<sip:daveh@localhost>;tag=1234');
41
+ msg.get('from').should.eql('<sip:daveh@localhost>;tag=1234');
42
+ });
43
+ it('should parse multiple headers into an array', function () {
44
+ var msg = new SipMessage(examples('invite'));
45
+ var via = msg.getParsedHeader('via');
46
+ via.should.be.an.array;
47
+ via.should.have.length(2);
48
+ });
49
+ it('should coalesce multiple calls to set', function () {
50
+ var msg = new SipMessage();
51
+ msg.set('via', 'SIP/2.0/UDP 10.1.10.101;branch=z9hG4bKac619477600');
52
+ msg.set('via', 'SIP/2.0/UDP 10.1.10.103;branch=z9hG4bKac619477603');
53
+ var via = msg.getParsedHeader('via');
54
+ via.should.be.an.array;
55
+ via.should.have.length(2);
56
+ parser.getStringifier('via')([via[1]]).should.eql('Via: SIP/2.0/UDP 10.1.10.103;branch=z9hG4bKac619477603\r\n');
57
+ });
58
+ it('should set multiple headers at once', function () {
59
+ var msg = new SipMessage();
60
+ msg.set({
61
+ to: '<sip:5753606@10.1.10.1>',
62
+ i: '619455480112200022407@10.1.10.101'
63
+ });
64
+ msg.get('call-id').should.eql('619455480112200022407@10.1.10.101');
65
+ msg.get('to').should.eql('<sip:5753606@10.1.10.1>');
66
+ })
67
+ it('should parse an invite request', function () {
68
+
69
+ var msg = new SipMessage(examples('invite'));
70
+ (msg.getParsedHeader('from').uri === null).should.be.false;
71
+ msg.type.should.eql('request');
72
+ (msg.body === null).should.be.false;
73
+ msg.canFormDialog.should.be.true;
74
+ });
75
+ it('should parse compact headers', function () {
76
+
77
+ var msg = new SipMessage(examples('invite-compact'));
78
+ msg.getParsedHeader('from').should.be.an.object;
79
+ msg.getParsedHeader('to').should.be.an.object;
80
+ msg.getParsedHeader('via').should.be.an.array;
81
+ });
82
+ it('should parse a response', function () {
83
+ var msg = new SipMessage(examples('200ok'));
84
+ msg.type.should.eql('response');
85
+ (msg.body === null).should.be.false;
86
+ });
87
+ it('should parse called number', function () {
88
+ var msg = new SipMessage(examples('invite'));
89
+ msg.calledNumber.should.eql('5753606');
90
+ });
91
+ it('should parse calling number', function () {
92
+ var msg = new SipMessage(examples('invite'));
93
+ msg.callingNumber.should.eql('4083084809');
94
+ });
95
+ it('should parse ipv4 dot decimal sip uri', function () {
96
+ var uri = parseUri('sip:104461@10.1.0.100:61219;rinstance=39ccb7d8db4387b1;transport=tcp');
97
+ uri.family.should.eql('ipv4');
98
+ uri.host.should.eql('10.1.0.100');
99
+ uri.port.should.eql(61219);
100
+ });
101
+ it('should parse ipv4 hostname sip uri', function () {
102
+ var uri = parseUri('sip:104461@foo.bar.com:61219;rinstance=39ccb7d8db4387b1;transport=tcp');
103
+ uri.family.should.eql('ipv4');
104
+ uri.host.should.eql('foo.bar.com');
105
+ uri.port.should.eql(61219);
106
+ });
107
+ it('should parse ipv6 sip uri', function () {
108
+ var uri = parseUri('sip:104461@[2601:182:cd00:d4c6:604b:16f1:3f5a:44f8]:61219;rinstance=39ccb7d8db4387b1;transport=tcp');
109
+ uri.family.should.eql('ipv6');
110
+ uri.host.should.eql('[2601:182:cd00:d4c6:604b:16f1:3f5a:44f8]');
111
+ uri.port.should.eql(61219);
112
+ });
113
+ it('should parse a sip uri with a dash or underscore', function () {
114
+ var uri = parseUri('sip:116751x0@cor10-san.sip.phone.com');
115
+ uri.family.should.eql('ipv4');
116
+ uri.host.should.eql('cor10-san.sip.phone.com');
117
+ uri.user.should.eql('116751x0');
118
+ });
119
+ it('should parse a sips uri', function () {
120
+ var uri = parseUri('sips:116751x0@cor10-san.sip.phone.com');
121
+ uri.family.should.eql('ipv4');
122
+ uri.host.should.eql('cor10-san.sip.phone.com');
123
+ uri.user.should.eql('116751x0');
124
+ uri.scheme.should.eql('sips');
125
+ });
126
+ it('should parse a multi-part header', function () {
127
+ var msg = new SipMessage(examples('siprec'));
128
+ msg.payload.length.should.eql(2);
129
+ });
130
+ it('should parse a multi-part header with whitespace before boundary', function () {
131
+ var msg = new SipMessage(examples('siprec2'));
132
+ msg.payload.length.should.eql(2);
133
+ });
134
+ it('should parse a multi-part header with quoted boundary', function () {
135
+ var msg = new SipMessage(examples('siprec3'));
136
+ msg.payload.length.should.eql(2);
137
+ });
138
+ it('should parse a sip uri with a dash or underscore', function () {
139
+ var uri = parseUri('sip:service@test_sipp-uas_1.com');
140
+ uri.family.should.eql('ipv4');
141
+ uri.host.should.eql('test_sipp-uas_1.com');
142
+ });
143
+ it('should parse calling name', function () {
144
+ var msg = new SipMessage();
145
+ msg.set('From', '"Dave" <sip:daveh@localhost>;tag=1234');
146
+ msg.get('From').should.eql('"Dave" <sip:daveh@localhost>;tag=1234');
147
+ msg.callingName.should.eql('Dave');
148
+ });
149
+ });
150
+