1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Default constructor.
 26  * @class
 27  * Note: do not directly instantiate AjxSoapDoc. Use one of the <code>create</code> methods instead
 28  * 
 29  * @see		AjxSoapDoc.create
 30  */
 31 AjxSoapDoc = function() {
 32 	this._soapURI = AjxSoapDoc._SOAP_URI;
 33 }
 34 
 35 AjxSoapDoc.prototype.isAjxSoapDoc = true;
 36 AjxSoapDoc.prototype.toString = function() { return "AjxSoapDoc"; };
 37 
 38 AjxSoapDoc._SOAP_URI = "http://www.w3.org/2003/05/soap-envelope";
 39 // AjxSoapDoc._SOAP_URI = "http://schemas.xmlsoap.org/soap/envelope/";
 40 AjxSoapDoc._XMLNS_URI = "http://www.w3.org/2000/xmlns";
 41 
 42 /**
 43  * Creates a SOAP document.
 44  * 
 45  * @param	{string}	method		the soap method
 46  * @param	{string}	namespace	the method namespace
 47  * @param	{string}	[namespaceId]	the namespace id
 48  * @param	{string}	[soapURI]	the SOAP uri
 49  * @return	{AjxSoapDoc}		the document
 50  */
 51 AjxSoapDoc.create =
 52 function(method, namespace, namespaceId, soapURI) {
 53 	var sd = new AjxSoapDoc();
 54 	sd._xmlDoc = AjxXmlDoc.create();
 55 	var d = sd._xmlDoc.getDoc();
 56 
 57 	if (!soapURI)
 58 		soapURI = AjxSoapDoc._SOAP_URI;
 59 	sd._soapURI = soapURI;
 60 
 61 	var useNS = d.createElementNS && !AjxEnv.isSafari;
 62 	var envEl = useNS ?  d.createElementNS(soapURI, "soap:Envelope") : d.createElement("soap:Envelope");
 63 	if (!useNS) envEl.setAttribute("xmlns:soap", soapURI);
 64 
 65 	d.appendChild(envEl);
 66 
 67 	var bodyEl = useNS ? d.createElementNS(soapURI, "soap:Body") : d.createElement("soap:Body");
 68 	envEl.appendChild(bodyEl);
 69 
 70 	sd._methodEl = namespace && useNS ?  d.createElementNS(namespace, method) : d.createElement(method);
 71 	if (namespace != null && !useNS) {
 72 		if (namespaceId == null)
 73 			sd._methodEl.setAttribute("xmlns", namespace);
 74 		else
 75 			sd._methodEl.setAttribute("xmlns:" + namespaceId, namespace);
 76 	}
 77 	bodyEl.appendChild(sd._methodEl);
 78 	return sd;
 79 };
 80 
 81 /**
 82  * Creates from a DOM object.
 83  * 
 84  * @param	{Object}	doc		the DOM object
 85  * @return	{AjxSoapDoc}		the document
 86  */
 87 AjxSoapDoc.createFromDom =
 88 function(doc) {
 89 	var sd = new AjxSoapDoc();
 90 	sd._xmlDoc = AjxXmlDoc.createFromDom(doc);
 91 	sd._methodEl = sd._check(sd._xmlDoc);
 92 	return sd;
 93 };
 94 
 95 /**
 96  * Creates from an XML object.
 97  * 
 98  * @param	{Object}	xml		the XML object
 99  * @return	{AjxSoapDoc}		the document
100  */
101 AjxSoapDoc.createFromXml =
102 function(xml) {
103 	var sd = new AjxSoapDoc();
104 	sd._xmlDoc = AjxXmlDoc.createFromXml(xml);
105 	sd._methodEl = sd._check(sd._xmlDoc);
106 	return sd;
107 };
108 
109 AjxSoapDoc.element2FaultObj =
110 function(el) {
111 	// If the element is not a SOAP fault, then return null
112 	var faultEl = el.firstChild;
113 	// Safari is bad at handling namespaces
114 	if (!AjxEnv.isSafari) {
115 		if (faultEl != null && faultEl.namespaceURI != AjxSoapDoc._SOAP_URI || faultEl.nodeName != (el.prefix + ":Fault"))
116 		return null;
117 	} else {
118 		if (faultEl != null && faultEl.nodeName != (el.prefix + ":Fault"))
119 			return null;
120 	}
121 	return new AjxSoapFault(faultEl);
122 };
123 
124 AjxSoapDoc.prototype.setMethodAttribute =
125 function(name, value) {
126 	this._methodEl.setAttribute(name, value);
127 };
128 
129 /**
130  * Creates arguments to pass within the envelope.  "value" can be a JS object
131  * or a scalar (string, number, etc.).
132  * <p>
133  * When "value" is a JS object, set() will call itself recursively in order to
134  * create a complex data structure.  Don't pass a "way-too-complicated" object
135  * ("value" should only contain references to simple JS objects, or better put,
136  * hashes--don't include a reference to the "window" object as it will kill
137  * your browser).
138  * <p>
139  * Example:
140  *
141  * <pre>
142  *    soapDoc.set("user_auth", {
143  *       user_name : "foo",
144  *       password  : "bar"
145  *    });
146  * </pre>
147  * 
148  * will create an XML like this under the method tag:
149  *
150  * <pre>
151  *    <user_auth>
152  *      <user_name>foo</user_name>
153  *      <password>bar</password>
154  *    </user_auth>
155  * </pre>
156  * 
157  * Of course, nesting other hashes is allowed and will work as expected.
158  * <p>
159  * NOTE: you can pass null for "name", in which case "value" is expected to be
160  * an object whose properties will be created directly under the method el.
161  * 
162  * @param	{string}	name	the name
163  * @param	{hash}	value		the attribute name/value pairs
164  * @param	{string}	[parent]	the parent element to append to
165  * @param	{string}	[namespace]	the namespace
166  * @return	{Element}	the node element
167  */
168 AjxSoapDoc.prototype.set =
169 function(name, value, parent, namespace) {
170 	var	doc = this.getDoc();
171 
172 	var useNS = doc.createElementNS && !AjxEnv.isSafari;
173 
174 	var	p = name
175 		? (namespace && useNS ? doc.createElementNS(namespace, name) : doc.createElement(name))
176 		: doc.createDocumentFragment();
177 
178     if ((namespace !== undefined) && (namespace !== null) && !useNS) {
179         p.setAttribute("xmlns", namespace);
180     }
181 
182 	if (value != null) {
183 		if (typeof value == "object") {
184 			for (var i in value) {
185                                 var val = value[i];
186                                 if (i.charAt(0) == "!") {
187                                         // attribute
188                                         p.setAttribute(i.substr(1), val);
189                                 } else if (val instanceof Array) {
190                                         // add multiple elements
191                                         for (var j = 0; j < val.length; ++j)
192                                                 this.set(i, val[j], p);
193                                 } else {
194 				        this.set(i, val, p);
195                                 }
196 			}
197 		} else {
198 			p.appendChild(doc.createTextNode(value));
199 		}
200 	}
201 	if (!parent)
202 		parent = this._methodEl;
203 	return parent.appendChild(p);
204 };
205 
206 /**
207  * Gets the method.
208  * 
209  * @return	{string}	the method
210  */
211 AjxSoapDoc.prototype.getMethod =
212 function() {
213 	return this._methodEl;
214 };
215 
216 /**
217  * Creates a header element.
218  * 
219  * @return	{Element}	the header element
220  */
221 AjxSoapDoc.prototype.createHeaderElement =
222 function() {
223 	var d = this._xmlDoc.getDoc();
224 	var envEl = d.firstChild;
225 	var header = this.getHeader();
226 	if (header != null) {
227 		throw new AjxSoapException("SOAP header already exists", AjxSoapException.ELEMENT_EXISTS, "AjxSoapDoc.prototype.createHeaderElement");
228 	}
229 	var useNS = d.createElementNS && !AjxEnv.isSafari;
230 	header = useNS ? d.createElementNS(this._soapURI, "soap:Header") : d.createElement("soap:Header")
231 	envEl.insertBefore(header, envEl.firstChild);
232 	return header;
233 };
234 
235 /**
236  * Gets the header.
237  * 
238  * @return	{Element}	the header or <code>null</code> if not created
239  */
240 AjxSoapDoc.prototype.getHeader =
241 function() {
242 	// fall back to getElementsByTagName in IE 8 and earlier
243 	var d = this._xmlDoc.getDoc();
244 	var nodeList = !d.getElementsByTagNameNS
245 		? (d.getElementsByTagName(d.firstChild.prefix + ":Header"))
246 		: (d.getElementsByTagNameNS(this._soapURI, "Header"));
247 
248 	return nodeList ? nodeList[0] : null;
249 };
250 
251 /**
252  * Gets the body.
253  * 
254  * @return	{Element}	the body element
255  */
256 AjxSoapDoc.prototype.getBody =
257 function() {
258 	// fall back to getElementsByTagName in IE 8 and earlier
259 	var d = this._xmlDoc.getDoc();
260 	var nodeList = !d.getElementsByTagNameNS
261 		? (d.getElementsByTagName(d.firstChild.prefix + ":Body"))
262 		: (d.getElementsByTagNameNS(this._soapURI, "Body"));
263 
264 	return nodeList ? nodeList[0] : null;
265 };
266 
267 AjxSoapDoc.prototype.getByTagName =
268 function(type) {
269 	if (type.indexOf(":") == -1)
270 		type = "soap:" + type;
271 
272 	var a = this.getDoc().getElementsByTagName(type);
273 
274 	if (a.length == 1)		return a[0];
275 	else if (a.length > 0)	return a;
276 	else					return null;
277 };
278 
279 // gimme a header, no exceptions.
280 AjxSoapDoc.prototype.ensureHeader =
281 function() {
282 	return (this.getHeader() || this.createHeaderElement());
283 };
284 
285 /**
286  * Gets the document.
287  * 
288  * @return	{Document}	the document
289  */
290 AjxSoapDoc.prototype.getDoc =
291 function() {
292 	return this._xmlDoc.getDoc();
293 };
294 
295 /**
296  * Adopts a node from another document to this document.
297  * 
298  * @param	{Element}	node		the node
299  * @private
300  */
301 AjxSoapDoc.prototype.adoptNode =
302 function(node) {
303 	// Older firefoxes throw not implemented error when you call adoptNode.
304 	if (AjxEnv.isFirefox3up || !AjxEnv.isFirefox) {
305 		try {
306 			var doc = this.getDoc();
307 			if (doc.adoptNode) {
308 				return doc.adoptNode(node, true);
309 			}
310 		} catch (ex) {
311 			// handle below by returning the input node.
312 		}
313 	}
314 	// call removeChild since Safari complains if you try to add an already
315 	// parented node to another document.
316 	return node.parentNode.removeChild(node);
317 };
318 
319 /**
320  * Gets the XML.
321  * 
322  * @return	{string}	the XML
323  */
324 AjxSoapDoc.prototype.getXml = function() {
325     return this._xmlDoc.getXml();
326 };
327 
328 // Very simple checking of soap doc. Should be made more comprehensive
329 AjxSoapDoc.prototype._check =
330 function(xmlDoc) {
331 	var doc = xmlDoc.getDoc();
332 	if (doc.childNodes.length != 1)
333 		throw new AjxSoapException("Invalid SOAP PDU", AjxSoapException.INVALID_PDU, "AjxSoapDoc.createFromXml:1");
334 
335 	// Check to make sure we have a soap envelope
336 	var el = doc.firstChild;
337 
338 	// Safari is bad at handling namespaces
339 	if (!AjxEnv.isSafari) {
340 		if (el.namespaceURI != AjxSoapDoc._SOAP_URI ||
341 			el.nodeName != (el.prefix + ":Envelope") ||
342 			(el.childNodes.length < 1 || el.childNodes.length > 2))
343 		{
344 			DBG.println("<font color=red>XML PARSE ERROR on RESPONSE:</font>");
345 			DBG.printRaw(doc.xml);
346 			throw new AjxSoapException("Invalid SOAP PDU", AjxSoapException.INVALID_PDU, "AjxSoapDoc.createFromXml:2");
347 		}
348 	} else {
349 		if (el.nodeName != (el.prefix + ":Envelope"))
350 			throw new AjxSoapException("Invalid SOAP PDU", AjxSoapException.INVALID_PDU, "AjxSoapDoc.createFromXml:2");
351 	}
352 };
353