/src/resiprocate/resip/stack/DialogInfoContents.cxx
Line | Count | Source (jump to first uncovered line) |
1 | | #if defined(HAVE_CONFIG_H) |
2 | | #include "config.h" |
3 | | #endif |
4 | | |
5 | | #include <time.h> |
6 | | #include <iomanip> |
7 | | |
8 | | #include "resip/stack/DialogInfoContents.hxx" |
9 | | #include "resip/stack/SipMessage.hxx" |
10 | | #include "resip/stack/Symbols.hxx" |
11 | | #include "rutil/XMLCursor.hxx" |
12 | | #include "rutil/Logger.hxx" |
13 | | #include "rutil/Inserter.hxx" |
14 | | #include "rutil/WinLeakCheck.hxx" |
15 | | |
16 | | using namespace resip; |
17 | | using namespace std; |
18 | | |
19 | | #define RESIPROCATE_SUBSYSTEM Subsystem::SIP |
20 | | |
21 | | const static Data BaseDialogInfoNamespaceUri("urn:ietf:params:xml:ns:dialog-info"); |
22 | | const static Data SharedAppearanceDialogInfoNamespaceUri("urn:ietf:params:xml:ns:sa-dialog-info"); |
23 | | const static Data SharedAppearanceDialogInfoNamespacePrefix("sa:"); |
24 | | |
25 | | const static Data DefaultEncodeIndent(" "); |
26 | | |
27 | | bool |
28 | | DialogInfoContents::init() |
29 | 4 | { |
30 | 4 | static ContentsFactory<DialogInfoContents> factory; |
31 | 4 | (void)factory; |
32 | 4 | return true; |
33 | 4 | } |
34 | | |
35 | | static const char* DialogInfoStateStrings[] = |
36 | | { |
37 | | "full", |
38 | | "partial" |
39 | | }; |
40 | | |
41 | | const char* |
42 | | DialogInfoContents::dialogInfoStateToString(const DialogInfoState& dialogInfoState) |
43 | 0 | { |
44 | 0 | return DialogInfoStateStrings[dialogInfoState]; |
45 | 0 | } |
46 | | |
47 | | DialogInfoContents::DialogInfoState |
48 | | DialogInfoContents::dialogInfoStateStringToEnum(const Data& dialogInfoStateString) |
49 | 0 | { |
50 | 0 | for (int i = 0; i < MaxDialogInfoState; i++) |
51 | 0 | { |
52 | 0 | if (isEqualNoCase(DialogInfoStateStrings[i], dialogInfoStateString)) |
53 | 0 | { |
54 | 0 | return (DialogInfoState)i; |
55 | 0 | } |
56 | 0 | } |
57 | 0 | return MaxDialogInfoState; |
58 | 0 | } |
59 | | |
60 | | static const char* DialogStateStrings[] = |
61 | | { |
62 | | "trying", |
63 | | "proceeding", |
64 | | "early", |
65 | | "confirmed", |
66 | | "terminated" |
67 | | }; |
68 | | |
69 | | const char* |
70 | | DialogInfoContents::dialogStateToString(const DialogState& dialogState) |
71 | 0 | { |
72 | 0 | return DialogStateStrings[dialogState]; |
73 | 0 | } |
74 | | |
75 | | DialogInfoContents::DialogState |
76 | | DialogInfoContents::dialogStateStringToEnum(const Data& dialogStateString) |
77 | 0 | { |
78 | 0 | for (int i = 0; i < MaxDialogState; i++) |
79 | 0 | { |
80 | 0 | if (isEqualNoCase(DialogStateStrings[i], dialogStateString)) |
81 | 0 | { |
82 | 0 | return (DialogState)i; |
83 | 0 | } |
84 | 0 | } |
85 | 0 | return MaxDialogState; |
86 | 0 | } |
87 | | |
88 | | static const char* DialogStateEventStrings[] = |
89 | | { |
90 | | "cancelled", |
91 | | "rejected", |
92 | | "replaced", |
93 | | "local-bye", |
94 | | "remote-bye", |
95 | | "error", |
96 | | "timeout" |
97 | | }; |
98 | | |
99 | | const char* |
100 | | DialogInfoContents::dialogStateEventToString(const DialogStateEvent& dialogStateEvent) |
101 | 0 | { |
102 | 0 | return DialogStateEventStrings[dialogStateEvent]; |
103 | 0 | } |
104 | | |
105 | | DialogInfoContents::DialogStateEvent |
106 | | DialogInfoContents::dialogStateEventStringToEnum(const Data& dialogStateEventString) |
107 | 0 | { |
108 | 0 | for (int i = 0; i < MaxOrUnsetDialogStateEvent; i++) |
109 | 0 | { |
110 | 0 | if (isEqualNoCase(DialogStateEventStrings[i], dialogStateEventString)) |
111 | 0 | { |
112 | 0 | return (DialogStateEvent)i; |
113 | 0 | } |
114 | 0 | } |
115 | 0 | return MaxOrUnsetDialogStateEvent; |
116 | 0 | } |
117 | | |
118 | | static const char* DirectionStrings[] = |
119 | | { |
120 | | "initiator", |
121 | | "recipient" |
122 | | }; |
123 | | |
124 | | const char* |
125 | | DialogInfoContents::directionToString(const Direction& direction) |
126 | 0 | { |
127 | 0 | return DirectionStrings[direction]; |
128 | 0 | } |
129 | | |
130 | | DialogInfoContents::Direction |
131 | | DialogInfoContents::directionStringToEnum(const Data& directionString) |
132 | 0 | { |
133 | 0 | for (int i = 0; i < MaxOrUnsetDirection; i++) |
134 | 0 | { |
135 | 0 | if (isEqualNoCase(DirectionStrings[i], directionString)) |
136 | 0 | { |
137 | 0 | return (Direction)i; |
138 | 0 | } |
139 | 0 | } |
140 | 0 | return MaxOrUnsetDirection; |
141 | 0 | } |
142 | | |
143 | | bool |
144 | | DialogInfoContents::compareTag(const Data& tagName, const Data& compareToTagNoPrefix, const Data& namespacePrefix) |
145 | 0 | { |
146 | 0 | if (namespacePrefix.empty()) |
147 | 0 | { |
148 | 0 | return tagName == compareToTagNoPrefix; |
149 | 0 | } |
150 | 0 | Data compareToTagWithPrefix(namespacePrefix); |
151 | 0 | compareToTagWithPrefix += compareToTagNoPrefix; |
152 | 0 | return tagName == compareToTagWithPrefix; |
153 | 0 | } |
154 | | |
155 | | const DialogInfoContents DialogInfoContents::Empty; |
156 | | |
157 | | DialogInfoContents::DialogInfoContents() |
158 | | : Contents(getStaticType()), mIndent(DefaultEncodeIndent), mVersion(0), mDialogInfoState(Partial) |
159 | 2 | { |
160 | 2 | } |
161 | | |
162 | | DialogInfoContents::DialogInfoContents(const Mime& contentType) |
163 | | : Contents(getStaticType()), mIndent(DefaultEncodeIndent), mVersion(0), mDialogInfoState(Partial) |
164 | 0 | { |
165 | 0 | } |
166 | | |
167 | | DialogInfoContents::DialogInfoContents(const HeaderFieldValue& hfv, const Mime& contentsType) |
168 | | : Contents(hfv, contentsType), mIndent(DefaultEncodeIndent), mVersion(0), mDialogInfoState(Partial) |
169 | 0 | { |
170 | 0 | } |
171 | | |
172 | | DialogInfoContents::~DialogInfoContents() |
173 | 0 | { |
174 | 0 | } |
175 | | |
176 | | Contents* |
177 | | DialogInfoContents::clone() const |
178 | 0 | { |
179 | 0 | return new DialogInfoContents(*this); |
180 | 0 | } |
181 | | |
182 | | const Mime& |
183 | | DialogInfoContents::getStaticType() |
184 | 4 | { |
185 | 4 | static Mime type("application","dialog-info+xml"); |
186 | 4 | return type; |
187 | 4 | } |
188 | | |
189 | | EncodeStream& |
190 | | DialogInfoContents::encodeParsed(EncodeStream& str) const |
191 | 0 | { |
192 | 0 | bool hasSharedAppearanceElements = false; |
193 | | |
194 | | // Check if any dialogs have shared appearance elements, if so, we need to add the namespace |
195 | 0 | for (DialogList::const_iterator it = mDialogs.begin(); it != mDialogs.end(); it++) |
196 | 0 | { |
197 | 0 | if (it->hasExclusive() || it->hasAppearance() || !it->getReplacedDialogs().empty() || !it->getJoinedDialogs().empty()) |
198 | 0 | { |
199 | 0 | hasSharedAppearanceElements = true; |
200 | 0 | break; // no need to continue, we found at least one |
201 | 0 | } |
202 | 0 | } |
203 | |
|
204 | 0 | str << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << Symbols::CRLF; |
205 | 0 | str << "<dialog-info xmlns=\"" << BaseDialogInfoNamespaceUri << "\"" << Symbols::CRLF; |
206 | 0 | if (hasSharedAppearanceElements) |
207 | 0 | { |
208 | 0 | str << " xmlns:" << SharedAppearanceDialogInfoNamespacePrefix << "=\"" << SharedAppearanceDialogInfoNamespaceUri << "\"" << Symbols::CRLF; |
209 | 0 | } |
210 | 0 | str << " version=\"" << mVersion << "\" state=\"" << dialogInfoStateToString(mDialogInfoState) << "\"" << Symbols::CRLF; |
211 | 0 | str << " entity=\"" << Data::from(mEntity).xmlCharDataEncode() << "\">" << Symbols::CRLF; |
212 | |
|
213 | 0 | for (DialogList::const_iterator it = mDialogs.begin(); it != mDialogs.end(); it++) |
214 | 0 | { |
215 | 0 | it->encodeParsed(str, mIndent); |
216 | 0 | } |
217 | |
|
218 | 0 | str << "</dialog-info>" << Symbols::CRLF; |
219 | |
|
220 | 0 | return str; |
221 | 0 | } |
222 | | |
223 | | bool |
224 | | DialogInfoContents::Dialog::getDialogElement(const Data& childElementName, Data& elementValue, int instance) const |
225 | 0 | { |
226 | 0 | bool found = false; |
227 | 0 | elementValue = ""; |
228 | 0 | std::pair <std::multimap<Data, Data>::const_iterator, std::multimap<Data, Data>::const_iterator> filteredList; |
229 | 0 | filteredList = mExtraDialogElements.equal_range(childElementName); |
230 | 0 | int matchCount = 0; |
231 | 0 | for (std::multimap<Data, Data>::const_iterator matchListIterator = filteredList.first; matchListIterator != filteredList.second; ++matchListIterator) |
232 | 0 | { |
233 | 0 | if(instance == matchCount) |
234 | 0 | { |
235 | 0 | elementValue = matchListIterator->second; |
236 | 0 | found = true; |
237 | 0 | break; |
238 | 0 | } |
239 | 0 | matchCount++; |
240 | 0 | } |
241 | |
|
242 | 0 | return(found); |
243 | 0 | } |
244 | | |
245 | | EncodeStream& |
246 | | DialogInfoContents::Dialog::encodeParsed(EncodeStream& str, const Data& indent) const |
247 | 0 | { |
248 | | // Encode dialog attributes |
249 | 0 | str << indent << "<dialog id=\"" << mId.xmlCharDataEncode() << "\""; |
250 | 0 | if (!mCallId.empty()) |
251 | 0 | { |
252 | 0 | str << " call-id=\"" << mCallId.xmlCharDataEncode() << "\""; |
253 | 0 | } |
254 | 0 | if (!mLocalTag.empty()) |
255 | 0 | { |
256 | 0 | str << Symbols::CRLF << indent << " local-tag=\"" << mLocalTag.xmlCharDataEncode() << "\""; |
257 | 0 | } |
258 | 0 | if (!mRemoteTag.empty()) |
259 | 0 | { |
260 | 0 | str << Symbols::CRLF << indent << " remote-tag=\"" << mRemoteTag.xmlCharDataEncode() << "\""; |
261 | 0 | } |
262 | 0 | if (mDirection != MaxOrUnsetDirection) |
263 | 0 | { |
264 | 0 | str << " direction=\"" << directionToString(mDirection) << "\""; |
265 | 0 | } |
266 | 0 | str << ">" << Symbols::CRLF; |
267 | | |
268 | | // Encode state element |
269 | 0 | str << indent << indent << "<state"; |
270 | 0 | if (mStateEvent != MaxOrUnsetDialogStateEvent) |
271 | 0 | { |
272 | 0 | str << " event=\"" << dialogStateEventToString(mStateEvent) << "\""; |
273 | 0 | } |
274 | 0 | if (mStateCode != 0) |
275 | 0 | { |
276 | 0 | str << " code=\"" << mStateCode << "\""; |
277 | 0 | } |
278 | 0 | str << ">" << dialogStateToString(mState) << "</state>" << Symbols::CRLF; |
279 | | |
280 | | // Encode duration element |
281 | 0 | if (mHasDuration) |
282 | 0 | { |
283 | 0 | str << indent << indent << "<duration>" << mDuration << "</duration>" << Symbols::CRLF; |
284 | 0 | } |
285 | | |
286 | | // Encode replaces element |
287 | 0 | if (!mReplaces.mCallId.empty()) |
288 | 0 | { |
289 | 0 | str << indent << indent << "<replaces call-id=\"" << mReplaces.mCallId.xmlCharDataEncode() << "\"" << Symbols::CRLF; |
290 | 0 | str << indent << indent << " local-tag=\"" << mReplaces.mLocalTag.xmlCharDataEncode() << "\" remote-tag=\"" << mReplaces.mRemoteTag.xmlCharDataEncode() << "\"/>" << Symbols::CRLF; |
291 | 0 | } |
292 | | |
293 | | // Encode referred by element |
294 | 0 | if (!mReferredBy.uri().host().empty()) |
295 | 0 | { |
296 | 0 | str << indent << indent; |
297 | 0 | encodeNameAddrElement(str, "referred-by", mReferredBy); |
298 | 0 | str << Symbols::CRLF; |
299 | 0 | } |
300 | | |
301 | | // Encode route set |
302 | 0 | if (!mRouteSet.empty()) |
303 | 0 | { |
304 | 0 | str << indent << indent << "<route-set>" << Symbols::CRLF; |
305 | 0 | for (NameAddrs::const_iterator itNA = mRouteSet.begin(); itNA != mRouteSet.end(); itNA++) |
306 | 0 | { |
307 | 0 | str << indent << indent << indent << "<hop>" << Data::from(itNA->uri()).xmlCharDataEncode() << "</hop>" << Symbols::CRLF; |
308 | 0 | } |
309 | 0 | str << indent << indent << "</route-set>" << Symbols::CRLF; |
310 | 0 | } |
311 | | |
312 | | // Encode local participant (if set) |
313 | 0 | mLocalParticipant.encode(str, "local", indent); |
314 | | |
315 | | // Encode remote participant (if set) |
316 | 0 | mRemoteParticipant.encode(str, "remote", indent); |
317 | | |
318 | | |
319 | | ////////////////////////////////////// |
320 | | // Encode RFC7463 Specific Elements |
321 | | ////////////////////////////////////// |
322 | | |
323 | | // Encode shared appearance number |
324 | 0 | if (mHasAppearance) |
325 | 0 | { |
326 | 0 | str << indent << indent << "<" << SharedAppearanceDialogInfoNamespacePrefix << "appearance>" << mAppearance << "</" << SharedAppearanceDialogInfoNamespacePrefix << "appearance>" << Symbols::CRLF; |
327 | 0 | } |
328 | | |
329 | | // Encode exclusive flag |
330 | 0 | if (mHasExclusive) |
331 | 0 | { |
332 | 0 | str << indent << indent << "<" << SharedAppearanceDialogInfoNamespacePrefix << "exclusive>" << (mExclusive ? "true" : "false") << "</" << SharedAppearanceDialogInfoNamespacePrefix << "exclusive>" << Symbols::CRLF; |
333 | 0 | } |
334 | | |
335 | | // Encode replaced-dialog's |
336 | 0 | for (auto replacedDialogsIterator = mReplacedDialogs.begin(); replacedDialogsIterator != mReplacedDialogs.end(); replacedDialogsIterator++) |
337 | 0 | { |
338 | 0 | str << indent << indent << "<" << SharedAppearanceDialogInfoNamespacePrefix << "replaced-dialog" << Symbols::CRLF |
339 | 0 | << indent << indent << indent << "call-id=\"" << replacedDialogsIterator->mCallId.xmlCharDataEncode() << "\"" << Symbols::CRLF |
340 | 0 | << indent << indent << indent << "local-tag=\"" << replacedDialogsIterator->mLocalTag.xmlCharDataEncode() << "\"" << Symbols::CRLF |
341 | 0 | << indent << indent << indent << "remote-tag=\"" << replacedDialogsIterator->mRemoteTag.xmlCharDataEncode() << "\"/>" << Symbols::CRLF; |
342 | 0 | } |
343 | | |
344 | | // Encode joined-dialog's |
345 | 0 | for (auto joinedDialogsIterator = mJoinedDialogs.begin(); joinedDialogsIterator != mJoinedDialogs.end(); joinedDialogsIterator++) |
346 | 0 | { |
347 | 0 | str << indent << indent << "<" << SharedAppearanceDialogInfoNamespacePrefix << "joined-dialog" << Symbols::CRLF |
348 | 0 | << indent << indent << indent << "call-id=\"" << joinedDialogsIterator->mCallId.xmlCharDataEncode() << "\"" << Symbols::CRLF |
349 | 0 | << indent << indent << indent << "local-tag=\"" << joinedDialogsIterator->mLocalTag.xmlCharDataEncode() << "\"" << Symbols::CRLF |
350 | 0 | << indent << indent << indent << "remote-tag=\"" << joinedDialogsIterator->mRemoteTag.xmlCharDataEncode() << "\"/>" << Symbols::CRLF; |
351 | 0 | } |
352 | | |
353 | | |
354 | | // User specific/non-standard Dialog elements |
355 | 0 | for(std::multimap<Data, Data>::const_iterator elementIterator = mExtraDialogElements.begin(); |
356 | 0 | elementIterator != mExtraDialogElements.end(); |
357 | 0 | elementIterator++) |
358 | 0 | { |
359 | 0 | DebugLog(<< "Dialog child element Name: \"" << elementIterator->first << "\" Value: \"" << elementIterator->second << "\""); |
360 | 0 | str << indent << indent << '<' << elementIterator->first << '>' |
361 | 0 | << elementIterator->second.xmlCharDataEncode() |
362 | 0 | << "</" << elementIterator->first << '>' << Symbols::CRLF; |
363 | 0 | } |
364 | |
|
365 | 0 | str << indent << "</dialog>" << Symbols::CRLF; |
366 | |
|
367 | 0 | return str; |
368 | 0 | } |
369 | | |
370 | | EncodeStream& |
371 | | DialogInfoContents::encodeNameAddrElement(EncodeStream& str, const char* elementName, const NameAddr& nameAddr) |
372 | 0 | { |
373 | 0 | str << "<" << elementName; |
374 | 0 | if (!nameAddr.displayName().empty()) |
375 | 0 | { |
376 | 0 | str << " display=\"" << nameAddr.displayName().xmlCharDataEncode() << "\""; |
377 | 0 | } |
378 | 0 | str << ">" << Data::from(nameAddr.uri()).xmlCharDataEncode() << "</" << elementName << ">"; |
379 | 0 | return str; |
380 | 0 | } |
381 | | |
382 | | void |
383 | | DialogInfoContents::Dialog::Participant::setTarget(const NameAddr& targetWithContactParams) |
384 | 0 | { |
385 | 0 | mTarget = targetWithContactParams.uri(); |
386 | 0 | Data paramsData; |
387 | 0 | { // scope so that stream flushs |
388 | 0 | DataStream dstr(paramsData); |
389 | 0 | targetWithContactParams.encodeParameters(dstr); |
390 | 0 | } |
391 | | // If there are parameters present - parse them |
392 | 0 | if (!paramsData.empty()) |
393 | 0 | { |
394 | 0 | ParseBuffer pb(paramsData); |
395 | 0 | pb.skipChar(); // Skip first ; that is always present |
396 | 0 | pb.skipWhitespace(); |
397 | |
|
398 | 0 | do |
399 | 0 | { |
400 | 0 | const char* anchor = pb.position(); |
401 | 0 | pb.skipToOneOf("=;"); |
402 | 0 | if (*pb.position() == '=') |
403 | 0 | { |
404 | 0 | Data name; |
405 | 0 | Data value; |
406 | 0 | pb.data(name, anchor); |
407 | 0 | pb.skipChar(); |
408 | 0 | pb.skipWhitespace(); |
409 | 0 | if (*pb.position() == '"') |
410 | 0 | { |
411 | | // Quoted Value |
412 | 0 | pb.skipChar(); |
413 | 0 | anchor = pb.position(); |
414 | 0 | pb.skipToChar('"'); |
415 | 0 | value = pb.data(anchor); |
416 | 0 | pb.skipChar(); |
417 | 0 | pb.skipToChar(';'); |
418 | 0 | } |
419 | 0 | else |
420 | 0 | { |
421 | | // Unquoted value |
422 | 0 | anchor = pb.position(); |
423 | 0 | pb.skipToChar(';'); |
424 | 0 | value = pb.data(anchor); |
425 | 0 | } |
426 | 0 | mTargetParams[name] = value; |
427 | 0 | } |
428 | 0 | else // ';' or eof |
429 | 0 | { |
430 | | // No equals operator - this is a boolean param |
431 | 0 | mTargetParams[pb.data(anchor)] = "true"; |
432 | 0 | pb.skipToChar(';'); |
433 | 0 | } |
434 | 0 | if (!pb.eof()) |
435 | 0 | { |
436 | | // Skip ; |
437 | 0 | pb.skipChar(); |
438 | 0 | } |
439 | 0 | } while (!pb.eof()); |
440 | 0 | } |
441 | 0 | } |
442 | | |
443 | | bool |
444 | | DialogInfoContents::Dialog::Participant::getTargetParam(const Data& name, Data& value) const |
445 | 0 | { |
446 | 0 | TargetParams::const_iterator it = mTargetParams.find(name); |
447 | 0 | if (it != mTargetParams.end()) |
448 | 0 | { |
449 | 0 | value = it->second; |
450 | 0 | return true; |
451 | 0 | } |
452 | 0 | return false; |
453 | 0 | } |
454 | | |
455 | | EncodeStream& |
456 | | DialogInfoContents::Dialog::Participant::encode(EncodeStream& str, const char* baseElementName, const Data& indent) const |
457 | 0 | { |
458 | | // Only encode if any of the optional sub elements are actually set |
459 | 0 | if (!mIdentity.uri().host().empty() || !mTarget.host().empty() || !mSessionDescription.empty() || mHasCSeq) |
460 | 0 | { |
461 | 0 | str << indent << indent << "<" << baseElementName << ">" << Symbols::CRLF; |
462 | 0 | if (!mIdentity.uri().host().empty()) |
463 | 0 | { |
464 | 0 | str << indent << indent << indent; |
465 | 0 | encodeNameAddrElement(str, "identity", mIdentity); |
466 | 0 | str << Symbols::CRLF; |
467 | 0 | } |
468 | 0 | if (!mTarget.host().empty()) |
469 | 0 | { |
470 | 0 | str << indent << indent << indent << "<target uri=\"" << Data::from(mTarget).xmlCharDataEncode() << "\""; |
471 | 0 | if (mTargetParams.empty()) |
472 | 0 | { |
473 | | // no params - just close tag |
474 | 0 | str << "/>" << Symbols::CRLF; |
475 | 0 | } |
476 | 0 | else |
477 | 0 | { |
478 | 0 | str << ">" << Symbols::CRLF; |
479 | 0 | for (TargetParams::const_iterator itParm = mTargetParams.begin(); itParm != mTargetParams.end(); itParm++) |
480 | 0 | { |
481 | 0 | str << indent << indent << indent << indent << "<param pname=\"" << itParm->first.xmlCharDataEncode() << "\" pval=\"" << itParm->second.xmlCharDataEncode() << "\"/>" << Symbols::CRLF; |
482 | 0 | } |
483 | 0 | str << indent << indent << indent << "</target>" << Symbols::CRLF; |
484 | 0 | } |
485 | 0 | } |
486 | 0 | if (!mSessionDescription.empty()) |
487 | 0 | { |
488 | | // SDP is multiline, so whitespace is important - don't format for "pretty" output |
489 | 0 | str << indent << indent << indent << "<session-description type=\"" << mSessionDescriptionType << "\">" << mSessionDescription.xmlCharDataEncode() << "</session-description>" << Symbols::CRLF; |
490 | 0 | } |
491 | 0 | if (mHasCSeq) |
492 | 0 | { |
493 | 0 | str << indent << indent << indent << "<cseq>" << mCSeq << "</cseq>" << Symbols::CRLF; |
494 | 0 | } |
495 | 0 | str << indent << indent << "</" << baseElementName << ">" << Symbols::CRLF; |
496 | 0 | } |
497 | 0 | return str; |
498 | 0 | } |
499 | | |
500 | | void |
501 | | DialogInfoContents::parse(ParseBuffer& pb) |
502 | 0 | { |
503 | 0 | XMLCursor xml(pb); |
504 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
505 | 0 | XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); |
506 | 0 | bool baseDialogInfoNamespaceUriFound = false; |
507 | 0 | for (; itAttr != attr.end(); itAttr++) |
508 | 0 | { |
509 | 0 | if (itAttr->first.prefix("xmlns")) |
510 | 0 | { |
511 | 0 | Data prefix; |
512 | 0 | ParseBuffer pb(itAttr->first); |
513 | 0 | pb.skipToChar(Symbols::COLON[0]); |
514 | 0 | if (!pb.eof()) |
515 | 0 | { |
516 | 0 | pb.skipChar(); |
517 | 0 | const char* anchor = pb.position(); |
518 | 0 | pb.skipToEnd(); |
519 | 0 | pb.data(prefix, anchor); |
520 | 0 | } |
521 | 0 | if (isEqualNoCase(itAttr->second, BaseDialogInfoNamespaceUri)) |
522 | 0 | { |
523 | 0 | baseDialogInfoNamespaceUriFound = true; |
524 | 0 | mBaseDialogInfoNamespacePrefix = prefix; |
525 | 0 | } |
526 | 0 | else if (isEqualNoCase(itAttr->second, SharedAppearanceDialogInfoNamespaceUri)) |
527 | 0 | { |
528 | 0 | mSharedAppearanceDialogInfoNamespacePrefix = prefix; |
529 | 0 | } |
530 | 0 | } |
531 | 0 | else if (itAttr->first == "version") |
532 | 0 | { |
533 | 0 | mVersion = itAttr->second.convertUnsignedLong(); |
534 | 0 | } |
535 | 0 | else if (itAttr->first == "state") |
536 | 0 | { |
537 | 0 | mDialogInfoState = dialogInfoStateStringToEnum(itAttr->second); |
538 | 0 | } |
539 | 0 | else if (itAttr->first == "entity") |
540 | 0 | { |
541 | 0 | mEntity = Uri(itAttr->second.xmlCharDataDecode()); // can throw! |
542 | 0 | } |
543 | 0 | else |
544 | 0 | { |
545 | 0 | DebugLog(<< "Unknown root attribute: " << itAttr->first << "=" << itAttr->second); |
546 | 0 | } |
547 | 0 | } |
548 | |
|
549 | 0 | if (!baseDialogInfoNamespaceUriFound) |
550 | 0 | { |
551 | 0 | WarningLog(<< "Base xmlns from RFC4235 was not found, expected: " << BaseDialogInfoNamespaceUri); |
552 | | // ?slg? - throw or be tolerant? |
553 | 0 | } |
554 | |
|
555 | 0 | if (xml.firstChild()) |
556 | 0 | { |
557 | 0 | do |
558 | 0 | { |
559 | 0 | if (compareTag(xml.getTag(), "dialog", mBaseDialogInfoNamespacePrefix)) |
560 | 0 | { |
561 | 0 | parseDialog(xml); |
562 | 0 | } |
563 | 0 | else |
564 | 0 | { |
565 | 0 | DebugLog(<< "Unknown root element: " << xml.getTag()); |
566 | 0 | } |
567 | 0 | } while (xml.nextSibling()); |
568 | 0 | xml.parent(); |
569 | 0 | } |
570 | 0 | } |
571 | | |
572 | | void |
573 | | DialogInfoContents::parseDialog(XMLCursor& xml) |
574 | 0 | { |
575 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
576 | 0 | XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); |
577 | 0 | Dialog dialog; |
578 | 0 | for (; itAttr != attr.end(); itAttr++) |
579 | 0 | { |
580 | 0 | if (itAttr->first == "id") |
581 | 0 | { |
582 | 0 | dialog.mId = itAttr->second.xmlCharDataDecode(); |
583 | 0 | } |
584 | 0 | else if (itAttr->first == "call-id") |
585 | 0 | { |
586 | 0 | dialog.mCallId = itAttr->second.xmlCharDataDecode(); |
587 | 0 | } |
588 | 0 | else if (itAttr->first == "local-tag") |
589 | 0 | { |
590 | 0 | dialog.mLocalTag = itAttr->second.xmlCharDataDecode(); |
591 | 0 | } |
592 | 0 | else if (itAttr->first == "remote-tag") |
593 | 0 | { |
594 | 0 | dialog.mRemoteTag = itAttr->second.xmlCharDataDecode(); |
595 | 0 | } |
596 | 0 | else if (itAttr->first == "direction") |
597 | 0 | { |
598 | 0 | dialog.mDirection = directionStringToEnum(itAttr->second); |
599 | 0 | } |
600 | 0 | else |
601 | 0 | { |
602 | 0 | DebugLog(<< "Unknown dialog attribute: " << itAttr->first << "=" << itAttr->second); |
603 | 0 | } |
604 | 0 | } |
605 | |
|
606 | 0 | if (dialog.mId.empty()) |
607 | 0 | { |
608 | 0 | WarningLog(<< "Dialog Id was not found for dialog element"); |
609 | | // ?slg? - throw or be tolerant? |
610 | 0 | } |
611 | |
|
612 | 0 | if (xml.firstChild()) |
613 | 0 | { |
614 | 0 | do |
615 | 0 | { |
616 | 0 | if (compareTag(xml.getTag(), "state", mBaseDialogInfoNamespacePrefix)) |
617 | 0 | { |
618 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
619 | 0 | for (itAttr = attr.begin(); itAttr != attr.end(); itAttr++) |
620 | 0 | { |
621 | 0 | if (itAttr->first == "event") |
622 | 0 | { |
623 | 0 | dialog.mStateEvent = dialogStateEventStringToEnum(itAttr->second); |
624 | 0 | } |
625 | 0 | else if (itAttr->first == "code") |
626 | 0 | { |
627 | 0 | dialog.mStateCode = itAttr->second.convertInt(); |
628 | 0 | } |
629 | 0 | else |
630 | 0 | { |
631 | 0 | DebugLog(<< "Unknown state attribute: " << itAttr->first << "=" << itAttr->second); |
632 | 0 | } |
633 | 0 | } |
634 | 0 | if (xml.firstChild()) |
635 | 0 | { |
636 | 0 | dialog.mState = dialogStateStringToEnum(xml.getValue()); |
637 | 0 | xml.parent(); |
638 | 0 | } |
639 | 0 | } |
640 | 0 | else if (compareTag(xml.getTag(), "duration", mBaseDialogInfoNamespacePrefix)) |
641 | 0 | { |
642 | 0 | if (xml.firstChild()) |
643 | 0 | { |
644 | 0 | dialog.mDuration = xml.getValue().convertUnsignedLong(); |
645 | 0 | dialog.mHasDuration = true; |
646 | 0 | xml.parent(); |
647 | 0 | } |
648 | 0 | } |
649 | 0 | else if (compareTag(xml.getTag(), "replaces", mBaseDialogInfoNamespacePrefix)) |
650 | 0 | { |
651 | 0 | parseDialogIdInfo(xml, dialog.mReplaces); |
652 | 0 | } |
653 | 0 | else if (compareTag(xml.getTag(), "referred-by", mBaseDialogInfoNamespacePrefix)) |
654 | 0 | { |
655 | 0 | parseNameAddrElement(xml, dialog.mReferredBy); |
656 | 0 | } |
657 | 0 | else if (compareTag(xml.getTag(), "route-set", mBaseDialogInfoNamespacePrefix)) |
658 | 0 | { |
659 | 0 | if (xml.firstChild()) |
660 | 0 | { |
661 | 0 | do |
662 | 0 | { |
663 | 0 | if (compareTag(xml.getTag(), "hop", mBaseDialogInfoNamespacePrefix)) |
664 | 0 | { |
665 | 0 | NameAddr nameAddr; |
666 | 0 | if (parseUriValue(xml, nameAddr.uri())) |
667 | 0 | { |
668 | 0 | dialog.mRouteSet.push_back(nameAddr); |
669 | 0 | } |
670 | 0 | } |
671 | 0 | else |
672 | 0 | { |
673 | 0 | DebugLog(<< "Unknown dialog/route-set element: " << xml.getTag()); |
674 | 0 | } |
675 | 0 | } while (xml.nextSibling()); |
676 | 0 | xml.parent(); |
677 | 0 | } |
678 | 0 | } |
679 | 0 | else if (compareTag(xml.getTag(), "local", mBaseDialogInfoNamespacePrefix)) |
680 | 0 | { |
681 | 0 | dialog.mLocalParticipant.parse(xml, mBaseDialogInfoNamespacePrefix); |
682 | 0 | } |
683 | 0 | else if (compareTag(xml.getTag(), "remote", mBaseDialogInfoNamespacePrefix)) |
684 | 0 | { |
685 | 0 | dialog.mRemoteParticipant.parse(xml, mBaseDialogInfoNamespacePrefix); |
686 | 0 | } |
687 | | // Now Look for RFC7463 tags |
688 | 0 | else if (compareTag(xml.getTag(), "appearance", mSharedAppearanceDialogInfoNamespacePrefix)) |
689 | 0 | { |
690 | 0 | if (xml.firstChild()) |
691 | 0 | { |
692 | 0 | dialog.mAppearance = xml.getValue().convertUnsignedLong(); |
693 | 0 | dialog.mHasAppearance = true; |
694 | 0 | xml.parent(); |
695 | 0 | } |
696 | 0 | } |
697 | 0 | else if (compareTag(xml.getTag(), "exclusive", mSharedAppearanceDialogInfoNamespacePrefix)) |
698 | 0 | { |
699 | 0 | if (xml.firstChild()) |
700 | 0 | { |
701 | 0 | dialog.mExclusive = isEqualNoCase(xml.getValue(), "true"); |
702 | 0 | dialog.mHasExclusive = true; |
703 | 0 | xml.parent(); |
704 | 0 | } |
705 | 0 | } |
706 | 0 | else if (compareTag(xml.getTag(), "replaced-dialog", mSharedAppearanceDialogInfoNamespacePrefix)) |
707 | 0 | { |
708 | 0 | Dialog::DialogIdInfo dialogIdInfo; |
709 | 0 | if (parseDialogIdInfo(xml, dialogIdInfo)) |
710 | 0 | { |
711 | 0 | dialog.mReplacedDialogs.push_back(dialogIdInfo); |
712 | 0 | } |
713 | 0 | } |
714 | 0 | else if (compareTag(xml.getTag(), "joined-dialog", mSharedAppearanceDialogInfoNamespacePrefix)) |
715 | 0 | { |
716 | 0 | Dialog::DialogIdInfo dialogIdInfo; |
717 | 0 | if (parseDialogIdInfo(xml, dialogIdInfo)) |
718 | 0 | { |
719 | 0 | dialog.mJoinedDialogs.push_back(dialogIdInfo); |
720 | 0 | } |
721 | 0 | } |
722 | 0 | else |
723 | 0 | { |
724 | 0 | Data elementName = xml.getTag(); |
725 | 0 | if (xml.firstChild()) |
726 | 0 | { |
727 | 0 | DebugLog(<< "Unknown dialog element: " << elementName << " value: " << xml.getValue().xmlCharDataDecode()); |
728 | 0 | dialog.addDialogElement(elementName, xml.getValue().xmlCharDataDecode()); |
729 | 0 | xml.parent(); |
730 | 0 | } |
731 | 0 | else |
732 | 0 | { |
733 | 0 | DebugLog(<< "Unknown dialog element: " << elementName); |
734 | 0 | } |
735 | 0 | } |
736 | 0 | } while (xml.nextSibling()); |
737 | 0 | xml.parent(); |
738 | 0 | } |
739 | | |
740 | | //DebugLog(<< "Pushing " << mDialogs.size() << "'th dialog " << dialog.mId << " parsed into DialogInfoCOntents::mDialogs"); |
741 | 0 | mDialogs.push_back(dialog); |
742 | 0 | } |
743 | | |
744 | | bool |
745 | | DialogInfoContents::parseDialogIdInfo(XMLCursor& xml, Dialog::DialogIdInfo& dialogIdInfo) |
746 | 0 | { |
747 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
748 | 0 | for (XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); itAttr != attr.end(); itAttr++) |
749 | 0 | { |
750 | 0 | if (itAttr->first == "call-id") |
751 | 0 | { |
752 | 0 | dialogIdInfo.mCallId = itAttr->second.xmlCharDataDecode(); |
753 | 0 | } |
754 | 0 | else if (itAttr->first == "local-tag") |
755 | 0 | { |
756 | 0 | dialogIdInfo.mLocalTag = itAttr->second.xmlCharDataDecode(); |
757 | 0 | } |
758 | 0 | else if (itAttr->first == "remote-tag") |
759 | 0 | { |
760 | 0 | dialogIdInfo.mRemoteTag = itAttr->second.xmlCharDataDecode(); |
761 | 0 | } |
762 | 0 | else |
763 | 0 | { |
764 | 0 | DebugLog(<< "Unknown dialog/replaces attribute: " << itAttr->first << "=" << itAttr->second); |
765 | 0 | } |
766 | 0 | } |
767 | 0 | return !dialogIdInfo.mCallId.empty() && !dialogIdInfo.mLocalTag.empty() && !dialogIdInfo.mRemoteTag.empty(); |
768 | 0 | } |
769 | | |
770 | | bool |
771 | | DialogInfoContents::parseUriValue(XMLCursor& xml, Uri& uri) |
772 | 0 | { |
773 | 0 | bool parseGood = false; |
774 | 0 | if (xml.firstChild()) |
775 | 0 | { |
776 | 0 | try |
777 | 0 | { |
778 | 0 | uri = Uri(xml.getValue().xmlCharDataDecode()); |
779 | 0 | parseGood = true; |
780 | 0 | } |
781 | 0 | catch (BaseException& ex) // ?slg? - catch here or not - let exception bubble up? or rethrow? |
782 | 0 | { |
783 | 0 | DebugLog(<< "Could not parse NameAddr value: " << xml.getValue().xmlCharDataDecode() << ": " << ex); |
784 | 0 | } |
785 | 0 | xml.parent(); |
786 | 0 | } |
787 | 0 | return parseGood; |
788 | 0 | } |
789 | | |
790 | | bool |
791 | | DialogInfoContents::parseNameAddrElement(XMLCursor& xml, NameAddr& nameAddr) |
792 | 0 | { |
793 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
794 | 0 | XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); |
795 | 0 | for (; itAttr != attr.end(); itAttr++) |
796 | 0 | { |
797 | 0 | if (itAttr->first == "display") |
798 | 0 | { |
799 | 0 | nameAddr.displayName() = itAttr->second.xmlCharDataDecode(); |
800 | 0 | } |
801 | 0 | else |
802 | 0 | { |
803 | 0 | DebugLog(<< "Unknown NameAddr attribute: " << itAttr->first << "=" << itAttr->second); |
804 | 0 | } |
805 | 0 | } |
806 | 0 | return parseUriValue(xml, nameAddr.uri()); |
807 | 0 | } |
808 | | |
809 | | void |
810 | | DialogInfoContents::Dialog::Participant::parse(XMLCursor& xml, const Data& namespacePrefix) |
811 | 0 | { |
812 | 0 | if (xml.firstChild()) |
813 | 0 | { |
814 | 0 | do |
815 | 0 | { |
816 | 0 | if (compareTag(xml.getTag(), "identity", namespacePrefix)) |
817 | 0 | { |
818 | 0 | parseNameAddrElement(xml, mIdentity); |
819 | 0 | } |
820 | 0 | else if (compareTag(xml.getTag(), "target", namespacePrefix)) |
821 | 0 | { |
822 | 0 | try |
823 | 0 | { |
824 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
825 | 0 | XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); |
826 | 0 | for (; itAttr != attr.end(); itAttr++) |
827 | 0 | { |
828 | 0 | if (itAttr->first == "uri") |
829 | 0 | { |
830 | 0 | mTarget = Uri(itAttr->second.xmlCharDataDecode()); |
831 | 0 | } |
832 | 0 | else |
833 | 0 | { |
834 | 0 | DebugLog(<< "Unknown dialog/participant/target attribute: " << itAttr->first << "=" << itAttr->second); |
835 | 0 | } |
836 | 0 | } |
837 | |
|
838 | 0 | if (xml.firstChild()) |
839 | 0 | { |
840 | 0 | do |
841 | 0 | { |
842 | 0 | if (compareTag(xml.getTag(), "param", namespacePrefix)) |
843 | 0 | { |
844 | 0 | parseParam(xml); |
845 | 0 | } |
846 | 0 | else |
847 | 0 | { |
848 | 0 | DebugLog(<< "Unknown dialog/particpant/target element: " << xml.getTag()); |
849 | 0 | } |
850 | 0 | } while (xml.nextSibling()); |
851 | 0 | xml.parent(); |
852 | 0 | } |
853 | 0 | } |
854 | 0 | catch (BaseException& ex) // ?slg? - catch here or not - let exception bubble up? or rethrow? |
855 | 0 | { |
856 | 0 | DebugLog(<< "Could not parse dialog/participatn/target uri value: " << xml.getValue().xmlCharDataDecode() << ": " << ex); |
857 | 0 | } |
858 | 0 | } |
859 | 0 | else if (compareTag(xml.getTag(), "session-description", namespacePrefix)) |
860 | 0 | { |
861 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
862 | 0 | XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); |
863 | 0 | for (; itAttr != attr.end(); itAttr++) |
864 | 0 | { |
865 | 0 | if (itAttr->first == "type") |
866 | 0 | { |
867 | 0 | mSessionDescriptionType = itAttr->second.xmlCharDataDecode(); |
868 | 0 | } |
869 | 0 | else |
870 | 0 | { |
871 | 0 | DebugLog(<< "Unknown dialog/participant/session-description attribute: " << itAttr->first << "=" << itAttr->second); |
872 | 0 | } |
873 | 0 | } |
874 | 0 | if (xml.firstChild()) |
875 | 0 | { |
876 | 0 | mSessionDescription = xml.getValue().xmlCharDataDecode(); |
877 | 0 | xml.parent(); |
878 | 0 | } |
879 | 0 | } |
880 | 0 | else if (compareTag(xml.getTag(), "cseq", namespacePrefix)) |
881 | 0 | { |
882 | 0 | if (xml.firstChild()) |
883 | 0 | { |
884 | 0 | mCSeq = xml.getValue().convertUnsignedLong(); |
885 | 0 | mHasCSeq = true; |
886 | 0 | xml.parent(); |
887 | 0 | } |
888 | 0 | } |
889 | 0 | else |
890 | 0 | { |
891 | 0 | DebugLog(<< "Unknown dialog participant element: " << xml.getTag()); |
892 | 0 | } |
893 | 0 | } while (xml.nextSibling()); |
894 | 0 | xml.parent(); |
895 | 0 | } |
896 | 0 | } |
897 | | |
898 | | void |
899 | | DialogInfoContents::Dialog::Participant::parseParam(XMLCursor& xml) |
900 | 0 | { |
901 | 0 | const XMLCursor::AttributeMap& attr = xml.getAttributes(); |
902 | 0 | XMLCursor::AttributeMap::const_iterator itAttr = attr.begin(); |
903 | 0 | Data name; |
904 | 0 | Data value; |
905 | 0 | for (; itAttr != attr.end(); itAttr++) |
906 | 0 | { |
907 | 0 | if (itAttr->first == "pname") |
908 | 0 | { |
909 | 0 | name = itAttr->second.xmlCharDataDecode(); |
910 | 0 | } |
911 | 0 | else if (itAttr->first == "pval") |
912 | 0 | { |
913 | 0 | value = itAttr->second.xmlCharDataDecode(); |
914 | 0 | } |
915 | 0 | else |
916 | 0 | { |
917 | 0 | DebugLog(<< "Unknown dialog/participant/target/param attribute: " << itAttr->first << "=" << itAttr->second); |
918 | 0 | } |
919 | 0 | } |
920 | 0 | if (!name.empty()) |
921 | 0 | { |
922 | 0 | mTargetParams[name] = value; |
923 | 0 | } |
924 | 0 | } |
925 | | |
926 | | void |
927 | | DialogInfoContents::addDialog(const Dialog& dialog) |
928 | 0 | { |
929 | 0 | checkParsed(); |
930 | | // Ensure id doesn't already exist |
931 | 0 | removeDialog(dialog.mId); |
932 | 0 | mDialogs.push_back(dialog); |
933 | 0 | } |
934 | | |
935 | | bool |
936 | | DialogInfoContents::removeDialog(const Data& id) |
937 | 0 | { |
938 | 0 | checkParsed(); |
939 | 0 | for (DialogList::iterator it = mDialogs.begin(); it != mDialogs.end(); it++) |
940 | 0 | { |
941 | 0 | if (it->mId == id) |
942 | 0 | { |
943 | 0 | mDialogs.erase(it); |
944 | 0 | return true; |
945 | 0 | } |
946 | 0 | } |
947 | 0 | return false; |
948 | 0 | } |
949 | | |
950 | | |
951 | | /* ==================================================================== |
952 | | * |
953 | | * Copyright (c) 2016 SIP Spectrum, Inc. All rights reserved. |
954 | | * |
955 | | * Redistribution and use in source and binary forms, with or without |
956 | | * modification, are permitted provided that the following conditions |
957 | | * are met: |
958 | | * |
959 | | * 1. Redistributions of source code must retain the above copyright |
960 | | * notice, this list of conditions and the following disclaimer. |
961 | | * |
962 | | * 2. Redistributions in binary form must reproduce the above copyright |
963 | | * notice, this list of conditions and the following disclaimer in |
964 | | * the documentation and/or other materials provided with the |
965 | | * distribution. |
966 | | * |
967 | | * 3. Neither the name of the author(s) nor the names of any contributors |
968 | | * may be used to endorse or promote products derived from this software |
969 | | * without specific prior written permission. |
970 | | * |
971 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND |
972 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
973 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
974 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE |
975 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
976 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
977 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
978 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
979 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
980 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
981 | | * SUCH DAMAGE. |
982 | | * |
983 | | * ==================================================================== |
984 | | * |
985 | | */ |
986 | | /* |
987 | | * vi: set shiftwidth=3 expandtab: |
988 | | */ |