1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file contains the command class. 27 */ 28 29 /** 30 * Creates a command. 31 * @class 32 * This class represents a command. 33 * 34 */ 35 ZmCsfeCommand = function() { 36 }; 37 38 ZmCsfeCommand.prototype.isZmCsfeCommand = true; 39 ZmCsfeCommand.prototype.toString = function() { return "ZmCsfeCommand"; }; 40 41 // Static properties 42 43 // Global settings for each CSFE command 44 ZmCsfeCommand._COOKIE_NAME = "ZM_AUTH_TOKEN"; 45 ZmCsfeCommand.serverUri = null; 46 47 ZmCsfeCommand._sessionId = null; // current session ID 48 ZmCsfeCommand._staleSession = {}; // old sessions 49 50 // Reasons for re-sending a request 51 ZmCsfeCommand.REAUTH = "reauth"; 52 ZmCsfeCommand.RETRY = "retry"; 53 54 // Static methods 55 56 /** 57 * Gets the auth token cookie. 58 * 59 * @return {String} the auth token 60 */ 61 ZmCsfeCommand.getAuthToken = 62 function() { 63 return AjxCookie.getCookie(document, ZmCsfeCommand._COOKIE_NAME); 64 }; 65 66 /** 67 * Sets the auth token cookie name. 68 * 69 * @param {String} cookieName the cookie name to user 70 */ 71 ZmCsfeCommand.setCookieName = 72 function(cookieName) { 73 ZmCsfeCommand._COOKIE_NAME = cookieName; 74 }; 75 76 /** 77 * Sets the server URI. 78 * 79 * @param {String} uri the URI 80 */ 81 ZmCsfeCommand.setServerUri = 82 function(uri) { 83 ZmCsfeCommand.serverUri = uri; 84 }; 85 86 /** 87 * Sets the auth token. 88 * 89 * @param {String} authToken the auth token 90 * @param {int} lifetimeMs the token lifetime in milliseconds 91 * @param {String} sessionId the session id 92 * @param {Boolean} secure <code>true</code> for secure 93 * 94 */ 95 ZmCsfeCommand.setAuthToken = 96 function(authToken, lifetimeMs, sessionId, secure) { 97 ZmCsfeCommand._curAuthToken = authToken; 98 if (lifetimeMs != null) { 99 var exp = null; 100 if(lifetimeMs > 0) { 101 exp = new Date(); 102 var lifetime = parseInt(lifetimeMs); 103 exp.setTime(exp.getTime() + lifetime); 104 } 105 AjxCookie.setCookie(document, ZmCsfeCommand._COOKIE_NAME, authToken, exp, "/", null, secure); 106 } else { 107 AjxCookie.deleteCookie(document, ZmCsfeCommand._COOKIE_NAME, "/"); 108 } 109 if (sessionId) { 110 ZmCsfeCommand.setSessionId(sessionId); 111 } 112 }; 113 114 /** 115 * Clears the auth token cookie. 116 * 117 */ 118 ZmCsfeCommand.clearAuthToken = 119 function() { 120 AjxCookie.deleteCookie(document, ZmCsfeCommand._COOKIE_NAME, "/"); 121 }; 122 123 /** 124 * Gets the session id. 125 * 126 * @return {String} the session id 127 */ 128 ZmCsfeCommand.getSessionId = 129 function() { 130 return ZmCsfeCommand._sessionId; 131 }; 132 133 /** 134 * Sets the session id and, if the session id is new, designates the previous 135 * session id as stale. 136 * 137 * @param {String} sessionId the session id 138 * 139 */ 140 ZmCsfeCommand.setSessionId = 141 function(sessionId) { 142 var sid = ZmCsfeCommand.extractSessionId(sessionId); 143 if (sid) { 144 if (sid && !ZmCsfeCommand._staleSession[sid]) { 145 if (sid != ZmCsfeCommand._sessionId) { 146 if (ZmCsfeCommand._sessionId) { 147 // Mark the old session as stale... 148 ZmCsfeCommand._staleSession[ZmCsfeCommand._sessionId] = true; 149 } 150 // ...before accepting the new session. 151 ZmCsfeCommand._sessionId = sid; 152 } 153 } 154 } 155 }; 156 157 ZmCsfeCommand.clearSessionId = 158 function() { 159 ZmCsfeCommand._sessionId = null; 160 }; 161 162 /** 163 * Isolates the parsing of the various forms of session types that we 164 * might have to handle. 165 * 166 * @param {mixed} session Any valid session object: string, number, object, 167 * or array. 168 * @return {Number|Null} If the input contained a valid session object, the 169 * session number will be returned. If the input is not valid, null will 170 * be returned. 171 */ 172 ZmCsfeCommand.extractSessionId = 173 function(session) { 174 var id; 175 176 if (session instanceof Array) { 177 // Array form 178 session = session[0].id; 179 } 180 else if (session && session.id) { 181 // Object form 182 session = session.id; 183 } 184 185 // We either have extracted the id or were given some primitive form. 186 // Whatever we have at this point, attempt conversion and clean up response. 187 id = parseInt(session, 10); 188 // Normalize response 189 if (isNaN(id)) { 190 id = null; 191 } 192 193 return id; 194 }; 195 196 /** 197 * Converts a fault to an exception. 198 * 199 * @param {Hash} fault the fault 200 * @param {Hash} params a hash of parameters 201 * @return {ZmCsfeException} the exception 202 */ 203 ZmCsfeCommand.faultToEx = 204 function(fault, params) { 205 var newParams = { 206 msg: AjxStringUtil.getAsString(fault.Reason.Text), 207 code: AjxStringUtil.getAsString(fault.Detail.Error.Code), 208 method: (params ? params.methodNameStr : null), 209 detail: AjxStringUtil.getAsString(fault.Code.Value), 210 data: fault.Detail.Error.a, 211 trace: (fault.Detail.Error.Trace || "") 212 }; 213 214 var request; 215 if (params) { 216 if (params.soapDoc) { 217 // note that we don't pretty-print XML if we get a soapDoc 218 newParams.request = params.soapDoc.getXml(); 219 } else if (params.jsonRequestObj) { 220 if (params.jsonRequestObj && params.jsonRequestObj.Header && params.jsonRequestObj.Header.context) { 221 params.jsonRequestObj.Header.context.authToken = "(removed)"; 222 } 223 newParams.request = AjxStringUtil.prettyPrint(params.jsonRequestObj, true); 224 } 225 } 226 227 return new ZmCsfeException(newParams); 228 }; 229 230 /** 231 * Gets the method name of the given request or response. 232 * 233 * @param {AjxSoapDoc|Object} request the request 234 * @return {String} the method name or "[unknown]" 235 */ 236 ZmCsfeCommand.getMethodName = 237 function(request) { 238 239 // SOAP request 240 var methodName = (request && request._methodEl && request._methodEl.tagName) 241 ? request._methodEl.tagName : null; 242 243 if (!methodName) { 244 for (var prop in request) { 245 if (/Request|Response$/.test(prop)) { 246 methodName = prop; 247 break; 248 } 249 } 250 } 251 return (methodName || "[unknown]"); 252 }; 253 254 /** 255 * Sends a SOAP request to the server and processes the response. The request can be in the form 256 * of a SOAP document, or a JSON object. 257 * 258 * @param {Hash} params a hash of parameters: 259 * @param {AjxSoapDoc} soapDoc the SOAP document that represents the request 260 * @param {Object} jsonObj the JSON object that represents the request (alternative to soapDoc) 261 * @param {Boolean} noAuthToken if <code>true</code>, the check for an auth token is skipped 262 * @param {Boolean} authToken authToken to use instead of the local one 263 * @param {String} serverUri the URI to send the request to 264 * @param {String} targetServer the host that services the request 265 * @param {Boolean} useXml if <code>true</code>, an XML response is requested 266 * @param {Boolean} noSession if <code>true</code>, no session info is included 267 * @param {String} changeToken the current change token 268 * @param {int} highestNotifySeen the sequence # of the highest notification we have processed 269 * @param {Boolean} asyncMode if <code>true</code>, request sent asynchronously 270 * @param {AjxCallback} callback the callback to run when response is received (async mode) 271 * @param {Boolean} logRequest if <code>true</code>, SOAP command name is appended to server URL 272 * @param {String} accountId the ID of account to execute on behalf of 273 * @param {String} accountName the name of account to execute on behalf of 274 * @param {Boolean} skipAuthCheck if <code>true</code> to skip auth check (i.e. do not check if auth token has changed) 275 * @param {constant} resend the reason for resending request 276 * @param {boolean} useStringify1 use JSON.stringify1 (gets around IE child win issue with Array) 277 * @param {boolean} emptyResponseOkay if true, empty or no response from server is not an erro 278 */ 279 ZmCsfeCommand.prototype.invoke = 280 function(params) { 281 this.cancelled = false; 282 if (!(params && (params.soapDoc || params.jsonObj))) { return; } 283 284 var requestStr = ZmCsfeCommand.getRequestStr(params); 285 286 var rpcCallback; 287 try { 288 var uri = (params.serverUri || ZmCsfeCommand.serverUri) + params.methodNameStr; 289 this._st = new Date(); 290 291 var requestHeaders = {"Content-Type": "application/soap+xml; charset=utf-8"}; 292 if (AjxEnv.isIE6 && (location.protocol == "https:")) { //bug 22829 293 requestHeaders["Connection"] = "Close"; 294 } 295 296 if (params.asyncMode) { 297 //DBG.println(AjxDebug.DBG1, "set callback for asynchronous response"); 298 rpcCallback = new AjxCallback(this, this._runCallback, [params]); 299 this._rpcId = AjxRpc.invoke(requestStr, uri, requestHeaders, rpcCallback); 300 } else { 301 //DBG.println(AjxDebug.DBG1, "parse response synchronously"); 302 var response = AjxRpc.invoke(requestStr, uri, requestHeaders); 303 return (!params.returnXml) ? (this._getResponseData(response, params)) : response; 304 } 305 } catch (ex) { 306 this._handleException(ex, params, rpcCallback); 307 } 308 }; 309 310 /** 311 * Sends a REST request to the server via GET and returns the response. 312 * 313 * @param {Hash} params a hash of parameters 314 * @param {String} params.restUri the REST URI to send the request to 315 * @param {Boolean} params.asyncMode if <code>true</code> request sent asynchronously 316 * @param {AjxCallback} params.callback the callback to run when response is received (async mode) 317 */ 318 ZmCsfeCommand.prototype.invokeRest = 319 function(params) { 320 321 if (!(params && params.restUri)) { return; } 322 323 var rpcCallback; 324 try { 325 this._st = new Date(); 326 if (params.asyncMode) { 327 rpcCallback = new AjxCallback(this, this._runCallback, [params]); 328 this._rpcId = AjxRpc.invoke(null, params.restUri, null, rpcCallback, true); 329 } else { 330 var response = AjxRpc.invoke(null, params.restUri, null, null, true); 331 return response.text; 332 } 333 } catch (ex) { 334 this._handleException(ex, params, rpcCallback); 335 } 336 }; 337 338 /** 339 * Cancels this request (which must be async). 340 * 341 */ 342 ZmCsfeCommand.prototype.cancel = 343 function() { 344 DBG.println("req", "CSFE cancel: " + this._rpcId); 345 if (!this._rpcId) { return; } 346 this.cancelled = true; 347 var req = AjxRpc.getRpcRequestById(this._rpcId); 348 if (req) { 349 req.cancel(); 350 if (AjxEnv.isFirefox3_5up) { 351 AjxRpc.removeRpcCtxt(req); 352 } 353 } 354 }; 355 356 /** 357 * Gets the request string. 358 * 359 * @param {Hash} params a hash of parameters 360 * @return {String} the request string 361 */ 362 ZmCsfeCommand.getRequestStr = 363 function(params) { 364 return params.soapDoc ? ZmCsfeCommand._getSoapRequestStr(params) : ZmCsfeCommand._getJsonRequestStr(params); 365 }; 366 367 /** 368 * @private 369 */ 370 ZmCsfeCommand._getJsonRequestStr = 371 function(params) { 372 373 var obj = {Header:{}, Body:params.jsonObj}; 374 375 var context = obj.Header.context = {_jsns:"urn:zimbra"}; 376 var ua_name = ["ZimbraWebClient - ", AjxEnv.browser, " (", AjxEnv.platform, ")"].join(""); 377 context.userAgent = {name:ua_name}; 378 if (ZmCsfeCommand.clientVersion) { 379 context.userAgent.version = ZmCsfeCommand.clientVersion; 380 } 381 if (params.noSession) { 382 context.nosession = {}; 383 } else { 384 var sessionId = ZmCsfeCommand.getSessionId(); 385 if (sessionId) { 386 context.session = {_content:sessionId, id:sessionId}; 387 } else { 388 context.session = {}; 389 } 390 } 391 if (params.targetServer) { 392 context.targetServer = {_content:params.targetServer}; 393 } 394 if (params.highestNotifySeen) { 395 context.notify = {seq:params.highestNotifySeen}; 396 } 397 if (params.changeToken) { 398 context.change = {token:params.changeToken, type:"new"}; 399 } 400 401 // if we're not checking auth token, we don't want token/acct mismatch 402 if (!params.skipAuthCheck) { 403 if (params.accountId) { 404 context.account = {_content:params.accountId, by:"id"} 405 } else if (params.accountName) { 406 context.account = {_content:params.accountName, by:"name"} 407 } 408 } 409 410 // Tell server what kind of response we want 411 if (params.useXml) { 412 context.format = {type:"xml"}; 413 } 414 415 params.methodNameStr = ZmCsfeCommand.getMethodName(params.jsonObj); 416 417 // Get auth token from cookie if required 418 if (!params.noAuthToken) { 419 var authToken = params.authToken || ZmCsfeCommand.getAuthToken(); 420 if (!authToken) { 421 throw new ZmCsfeException(ZMsg.authTokenRequired, ZmCsfeException.NO_AUTH_TOKEN, params.methodNameStr); 422 } 423 if (ZmCsfeCommand._curAuthToken && !params.skipAuthCheck && 424 (params.resend != ZmCsfeCommand.REAUTH) && (authToken != ZmCsfeCommand._curAuthToken)) { 425 throw new ZmCsfeException(ZMsg.authTokenChanged, ZmCsfeException.AUTH_TOKEN_CHANGED, params.methodNameStr); 426 } 427 context.authToken = ZmCsfeCommand._curAuthToken = authToken; 428 } 429 else if (ZmCsfeCommand.noAuth) { 430 throw new ZmCsfeException(ZMsg.authRequired, ZmCsfeException.NO_AUTH_TOKEN, params.methodNameStr); 431 } 432 433 if (window.csrfToken) { 434 context.csrfToken = window.csrfToken; 435 } 436 437 AjxDebug.logSoapMessage(params); 438 DBG.dumpObj(AjxDebug.DBG1, obj); 439 440 params.jsonRequestObj = obj; 441 442 var requestStr = (params.useStringify1 ? 443 JSON.stringify1(obj) : JSON.stringify(obj)); 444 445 // bug 74240: escape non-ASCII characters to prevent the browser from 446 // combining decomposed characters in paths 447 return AjxStringUtil.jsEncode(requestStr) 448 }; 449 450 /** 451 * @private 452 */ 453 ZmCsfeCommand._getSoapRequestStr = 454 function(params) { 455 456 var soapDoc = params.soapDoc; 457 458 if (!params.resend) { 459 460 // Add the SOAP header and context 461 var hdr = soapDoc.createHeaderElement(); 462 var context = soapDoc.set("context", null, hdr, "urn:zimbra"); 463 464 var ua = soapDoc.set("userAgent", null, context); 465 var name = ["ZimbraWebClient - ", AjxEnv.browser, " (", AjxEnv.platform, ")"].join(""); 466 ua.setAttribute("name", name); 467 if (ZmCsfeCommand.clientVersion) { 468 ua.setAttribute("version", ZmCsfeCommand.clientVersion); 469 } 470 471 if (params.noSession) { 472 soapDoc.set("nosession", null, context); 473 } else { 474 var sessionId = ZmCsfeCommand.getSessionId(); 475 var si = soapDoc.set("session", null, context); 476 if (sessionId) { 477 si.setAttribute("id", sessionId); 478 } 479 } 480 if (params.targetServer) { 481 soapDoc.set("targetServer", params.targetServer, context); 482 } 483 if (params.highestNotifySeen) { 484 var notify = soapDoc.set("notify", null, context); 485 notify.setAttribute("seq", params.highestNotifySeen); 486 } 487 if (params.changeToken) { 488 var ct = soapDoc.set("change", null, context); 489 ct.setAttribute("token", params.changeToken); 490 ct.setAttribute("type", "new"); 491 } 492 493 // if we're not checking auth token, we don't want token/acct mismatch 494 if (!params.skipAuthCheck) { 495 if (params.accountId) { 496 var acc = soapDoc.set("account", params.accountId, context); 497 acc.setAttribute("by", "id"); 498 } else if (params.accountName) { 499 var acc = soapDoc.set("account", params.accountName, context); 500 acc.setAttribute("by", "name"); 501 } 502 } 503 504 if (params.skipExpiredToken) { 505 var tokenControl = soapDoc.set("authTokenControl", null, context); 506 tokenControl.setAttribute("voidOnExpired", "1"); 507 } 508 // Tell server what kind of response we want 509 if (!params.useXml) { 510 var js = soapDoc.set("format", null, context); 511 js.setAttribute("type", "js"); 512 } 513 } 514 515 params.methodNameStr = ZmCsfeCommand.getMethodName(soapDoc); 516 517 // Get auth token from cookie if required 518 if (!params.noAuthToken) { 519 var authToken = params.authToken || ZmCsfeCommand.getAuthToken(); 520 if (!authToken) { 521 throw new ZmCsfeException(ZMsg.authTokenRequired, ZmCsfeException.NO_AUTH_TOKEN, params.methodNameStr); 522 } 523 if (ZmCsfeCommand._curAuthToken && !params.skipAuthCheck && 524 (params.resend != ZmCsfeCommand.REAUTH) && (authToken != ZmCsfeCommand._curAuthToken)) { 525 throw new ZmCsfeException(ZMsg.authTokenChanged, ZmCsfeException.AUTH_TOKEN_CHANGED, params.methodNameStr); 526 } 527 ZmCsfeCommand._curAuthToken = authToken; 528 if (params.resend == ZmCsfeCommand.REAUTH) { 529 // replace old auth token with current one 530 var nodes = soapDoc.getDoc().getElementsByTagName("authToken"); 531 if (nodes && nodes.length == 1) { 532 DBG.println(AjxDebug.DBG1, "Re-auth: replacing auth token"); 533 nodes[0].firstChild.data = authToken; 534 } else { 535 // can't find auth token, just add it to context element 536 nodes = soapDoc.getDoc().getElementsByTagName("context"); 537 if (nodes && nodes.length == 1) { 538 DBG.println(AjxDebug.DBG1, "Re-auth: re-adding auth token"); 539 soapDoc.set("authToken", authToken, nodes[0]); 540 } else { 541 DBG.println(AjxDebug.DBG1, "Re-auth: could not find context!"); 542 } 543 } 544 } else if (!params.resend){ 545 soapDoc.set("authToken", authToken, context); 546 } 547 } 548 else if (ZmCsfeCommand.noAuth && !params.ignoreAuthToken) { 549 throw new ZmCsfeException(ZMsg.authRequired, ZmCsfeException.NO_AUTH_TOKEN, params.methodNameStr); 550 } 551 552 if (window.csrfToken) { 553 soapDoc.set("csrfToken", window.csrfToken, context); 554 } 555 556 AjxDebug.logSoapMessage(params); 557 DBG.printXML(AjxDebug.DBG1, soapDoc.getXml()); 558 559 return soapDoc.getXml(); 560 }; 561 562 /** 563 * Runs the callback that was passed to invoke() for an async command. 564 * 565 * @param {AjxCallback} callback the callback to run with response data 566 * @param {Hash} params a hash of parameters (see method invoke()) 567 * 568 * @private 569 */ 570 ZmCsfeCommand.prototype._runCallback = 571 function(params, result) { 572 if (!result) { return; } 573 if (this.cancelled && params.skipCallbackIfCancelled) { return; } 574 575 var response; 576 if (result instanceof ZmCsfeResult) { 577 response = result; // we already got an exception and packaged it 578 } else { 579 response = this._getResponseData(result, params); 580 } 581 this._en = new Date(); 582 583 if (params.callback && response) { 584 params.callback.run(response); 585 } else if (!params.emptyResponseOkay) { 586 DBG.println(AjxDebug.DBG1, "ZmCsfeCommand.prototype._runCallback: Missing callback!"); 587 } 588 }; 589 590 /** 591 * Takes the response to an RPC request and returns a JS object with the response data. 592 * 593 * @param {Object} response the RPC response with properties "text" and "xml" 594 * @param {Hash} params a hash of parameters (see method invoke()) 595 */ 596 ZmCsfeCommand.prototype._getResponseData = 597 function(response, params) { 598 this._en = new Date(); 599 DBG.println(AjxDebug.DBG1, "ROUND TRIP TIME: " + (this._en.getTime() - this._st.getTime())); 600 601 var result = new ZmCsfeResult(); 602 var xmlResponse = false; 603 var restResponse = Boolean(params.restUri); 604 var respDoc = null; 605 606 // check for un-parseable HTML error response from server 607 if (!response.success && !response.xml && (/<html/i.test(response.text))) { 608 // bad XML or JS response that had no fault 609 var ex = new ZmCsfeException(null, ZmCsfeException.CSFE_SVC_ERROR, params.methodNameStr, "HTTP response status " + response.status); 610 if (params.asyncMode) { 611 result.set(ex, true); 612 return result; 613 } else { 614 throw ex; 615 } 616 } 617 618 if (typeof(response.text) == "string" && response.text.indexOf("{") == 0) { 619 respDoc = response.text; 620 } else if (!restResponse) { 621 // an XML response if we requested one, or a fault 622 try { 623 xmlResponse = true; 624 if (!(response.text || (response.xml && (typeof response.xml) == "string"))) { 625 if (params.emptyResponseOkay) { 626 return null; 627 } 628 else { 629 // If we can't reach the server, req returns immediately with an empty response rather than waiting and timing out 630 throw new ZmCsfeException(null, ZmCsfeException.EMPTY_RESPONSE, params.methodNameStr); 631 } 632 } 633 // responseXML is empty under IE 634 respDoc = (AjxEnv.isIE || response.xml == null) ? AjxSoapDoc.createFromXml(response.text) : 635 AjxSoapDoc.createFromDom(response.xml); 636 } catch (ex) { 637 DBG.dumpObj(AjxDebug.DBG1, ex); 638 if (params.asyncMode) { 639 result.set(ex, true); 640 return result; 641 } else { 642 throw ex; 643 } 644 } 645 if (!respDoc) { 646 var ex = new ZmCsfeException(null, ZmCsfeException.SOAP_ERROR, params.methodNameStr, "Bad XML response doc"); 647 DBG.dumpObj(AjxDebug.DBG1, ex); 648 if (params.asyncMode) { 649 result.set(ex, true); 650 return result; 651 } else { 652 throw ex; 653 } 654 } 655 } 656 657 var obj = restResponse ? response.text : {}; 658 659 if (xmlResponse) { 660 DBG.printXML(AjxDebug.DBG1, respDoc.getXml()); 661 obj = respDoc._xmlDoc.toJSObject(true, false, true); 662 } else if (!restResponse) { 663 try { 664 obj = JSON.parse(respDoc); 665 } catch (ex) { 666 if (ex.name == "SyntaxError") { 667 ex = new ZmCsfeException(null, ZmCsfeException.BAD_JSON_RESPONSE, params.methodNameStr, respDoc); 668 AjxDebug.println(AjxDebug.BAD_JSON, "bad json. respDoc=" + respDoc); 669 } 670 DBG.dumpObj(AjxDebug.DBG1, ex); 671 if (params.asyncMode) { 672 result.set(ex, true); 673 return result; 674 } else { 675 throw ex; 676 } 677 } 678 679 } 680 681 params.methodNameStr = ZmCsfeCommand.getMethodName(obj.Body); 682 AjxDebug.logSoapMessage(params); 683 DBG.dumpObj(AjxDebug.DBG1, obj, -1); 684 685 var fault = obj && obj.Body && obj.Body.Fault; 686 if (fault) { 687 // JS response with fault 688 if (AjxUtil.isString(fault) && fault.indexOf("<")==0) { // We got an xml string 689 fault = AjxXmlDoc.createFromXml(fault).toJSObject(true, false, true); 690 } 691 var ex = ZmCsfeCommand.faultToEx(fault, params); 692 if (params.asyncMode) { 693 result.set(ex, true, obj.Header); 694 return result; 695 } else { 696 throw ex; 697 } 698 } else if (!response.success) { 699 // bad XML or JS response that had no fault 700 var ex = new ZmCsfeException(null, ZmCsfeException.CSFE_SVC_ERROR, params.methodNameStr, "HTTP response status " + response.status); 701 if (params.asyncMode) { 702 result.set(ex, true); 703 return result; 704 } else { 705 throw ex; 706 } 707 } else { 708 // good response 709 if (params.asyncMode) { 710 result.set(obj); 711 } 712 } 713 714 // check for new session ID 715 var session = obj.Header && obj.Header.context && obj.Header.context.session; 716 ZmCsfeCommand.setSessionId(session); 717 718 return params.asyncMode ? result : obj; 719 }; 720 721 /** 722 * @private 723 */ 724 ZmCsfeCommand.prototype._handleException = 725 function(ex, params, callback) { 726 if (!(ex && (ex instanceof ZmCsfeException || ex instanceof AjxSoapException || ex instanceof AjxException))) { 727 var newEx = new ZmCsfeException(); 728 newEx.method = params.methodNameStr || params.restUri; 729 newEx.detail = ex ? ex.toString() : "undefined exception"; 730 newEx.code = ZmCsfeException.UNKNOWN_ERROR; 731 newEx.msg = "Unknown Error"; 732 ex = newEx; 733 } 734 if (params.asyncMode) { 735 callback.run(new ZmCsfeResult(ex, true)); 736 } else { 737 throw ex; 738 } 739 }; 740