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