1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * @constructor 27 * @class 28 * This class encapsulates the XML HTTP request, hiding differences between 29 * browsers. The internal request object depends on the browser. While it is 30 * possible to use this class directly, {@link AjxRpc} provides a managed interface 31 * to this class 32 * 33 * @author Ross Dargahi 34 * @author Conrad Damon 35 * 36 * @param {string} [id] the ID to identify this object 37 * 38 * @see AjxRpc 39 * 40 */ 41 AjxRpcRequest = function(id) { 42 if (!AjxRpcRequest.__inited) { 43 AjxRpcRequest.__init(); 44 } 45 46 /** 47 * The id for this object. 48 */ 49 this.id = id; 50 this.__httpReq = AjxRpcRequest.__msxmlVers 51 ? (new ActiveXObject(AjxRpcRequest.__msxmlVers)) 52 : (new XMLHttpRequest()); 53 }; 54 55 AjxRpcRequest.prototype.isAjxRpcRequest = true; 56 AjxRpcRequest.prototype.toString = function() { return "AjxRpcRequest"; }; 57 58 AjxRpcRequest.TIMEDOUT = -1000; // Timed out exception 59 AjxRpcRequest.HTTP_GET = "get"; //HTTP GET 60 AjxRpcRequest.HTTP_POST = "post"; //HTTP POST 61 AjxRpcRequest.HTTP_PUT = "put"; //HTTP PUT 62 AjxRpcRequest.HTTP_DELETE = "delete"; //HTTP DELETE 63 64 AjxRpcRequest.__inited = false; 65 AjxRpcRequest.__msxmlVers = null; 66 67 68 /** 69 * Sends this request to the target URL. If there is a callback, the request is 70 * performed asynchronously. 71 * 72 * @param {string} [requestStr] the HTTP request string/document 73 * @param {string} serverUrl the request target 74 * @param {array} [requestHeaders] an array of HTTP request headers 75 * @param {AjxCallback} callback the callback for asynchronous requests. This callback 76 * will be invoked when the requests completes. It will be passed the same 77 * values as when this method is invoked synchronously (see the return values 78 * below) with the exception that if the call times out (see timeout param 79 * below), then the object passed to the callback will be the same as in the 80 * error case with the exception that the status will be set to 81 * {@link AjxRpcRequest.TIMEDOUT}. 82 * @param {Constant} [method] the HTTP method -- GET, POST, PUT, DELETE. if <code>true</code>, use get method for backward compatibility 83 * @param {number} [timeout] the timeout (in milliseconds) after which the request is canceled 84 * 85 * @return {object|hash} if invoking in asynchronous mode, then it will return the id of the 86 * underlying {@link AjxRpcRequest} object. Else if invoked synchronously, if 87 * there is no error (i.e. we get a HTTP result code of 200 from the server), 88 * an object with the following attributes is returned 89 * <ul> 90 * <li>text - the string response text</li> 91 * <li>xml - the string response xml</li> 92 * <li>success - boolean set to true</li> 93 * </ul> 94 * If there is an eror, then the following will be returned 95 * <ul> 96 * <li>text - the string response text<li> 97 * <li>xml - the string response xml </li> 98 * <li>success - boolean set to <code>false</code></li> 99 * <li>status - HTTP status</li> 100 * </ul> 101 * 102 * @throws {AjxException.NETWORK_ERROR} a network error occurs 103 * @throws {AjxException.UNKNOWN_ERROR} an unknown error occurs 104 * 105 * @see AjxRpc.invoke 106 */ 107 AjxRpcRequest.prototype.invoke = 108 function(requestStr, serverUrl, requestHeaders, callback, method, timeout) { 109 110 111 var asyncMode = (callback != null); 112 this.methodName = serverUrl || ""; 113 114 // An exception here will be caught by AjxRpc.invoke 115 var httpMethod = AjxRpcRequest.HTTP_POST; 116 if (method) { 117 httpMethod = method === true ? AjxRpcRequest.HTTP_GET : method; 118 } 119 120 if (window.csrfToken && 121 (httpMethod === AjxRpcRequest.HTTP_POST || 122 httpMethod === AjxRpcRequest.HTTP_PUT || 123 httpMethod === AjxRpcRequest.HTTP_DELETE)) { 124 125 requestHeaders = requestHeaders || {}; 126 requestHeaders["X-Zimbra-Csrf-Token"] = window.csrfToken; 127 128 } 129 130 this.__httpReq.open(httpMethod, serverUrl, asyncMode); 131 132 if (asyncMode) { 133 if (timeout) { 134 var action = new AjxTimedAction(this, this.__handleTimeout, [callback]); 135 callback._timedActionId = AjxTimedAction.scheduleAction(action, timeout); 136 } 137 var tempThis = this; 138 this.__httpReq.onreadystatechange = function(ev) { 139 if (window.AjxRpcRequest) { 140 AjxRpcRequest.__handleResponse(tempThis, callback); 141 } 142 } 143 } else { 144 // IE appears to run handler even on sync requests, so we need to clear it 145 this.__httpReq.onreadystatechange = function(ev) {}; 146 } 147 148 if (requestHeaders) { 149 for (var i in requestHeaders) { 150 if (requestHeaders.hasOwnProperty(i)) { 151 this.__httpReq.setRequestHeader(i, requestHeaders[i]); 152 } 153 } 154 } 155 156 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " RPC send: " + this.id); 157 this.__httpReq.send(requestStr); 158 if (asyncMode) { 159 return this.id; 160 } else { 161 if (this.__httpReq.status == 200 || this.__httpReq.status == 201) { 162 return {text:this.__httpReq.responseText, xml:this.__httpReq.responseXML, success:true}; 163 } else { 164 return {text:this.__httpReq.responseText, xml:this.__httpReq.responseXML, success:false, status:this.__httpReq.status}; 165 } 166 } 167 }; 168 169 /** 170 * Cancels a pending request. 171 * 172 */ 173 AjxRpcRequest.prototype.cancel = 174 function() { 175 AjxRpc.freeRpcCtxt(this); 176 if (AjxEnv.isFirefox3_5up) { 177 // bug 55911 178 this.__httpReq.onreadystatechange = function(){}; 179 } 180 this.__httpReq.abort(); 181 }; 182 183 /** 184 * Handler that runs when an asynchronous request timesout. 185 * 186 * @param {AjxCallback} callback the callback to run after timeout 187 * 188 * @private 189 */ 190 AjxRpcRequest.prototype.__handleTimeout = 191 function(callback) { 192 this.cancel(); 193 callback.run( {text:null, xml:null, success:false, status:AjxRpcRequest.TIMEDOUT} ); 194 }; 195 196 /** 197 * Handler that runs when an asynchronous response has been received. It runs a 198 * callback to initiate the response handling. 199 * 200 * @param {AjxRpcRequest} req the request that generated the response 201 * @param {AjxCallback} callback the callback to run after response is received 202 * 203 * @private 204 */ 205 AjxRpcRequest.__handleResponse = 206 function(req, callback) { 207 208 try { 209 210 if (!req || !req.__httpReq) { 211 212 req.cancel(); 213 214 // If IE receives a 500 error, the object reference can be lost 215 DBG.println(AjxDebug.DBG1, "Async RPC request: Lost request object!!!"); 216 AjxDebug.println(AjxDebug.RPC, AjxDebug._getTimeStamp() + " Async RPC request: Lost request object!!!"); 217 callback.run( {text:null, xml:null, success:false, status:500} ); 218 AjxRpc.freeRpcCtxt(req); 219 return; 220 } 221 222 if (req.__httpReq.readyState == 4) { 223 if (callback._timedActionId !== null) { 224 AjxTimedAction.cancelAction(callback._timedActionId); 225 } 226 227 var status = 500; 228 try { 229 status = req.__httpReq.status; 230 } catch (ex) { 231 // Use default status of 500 above. 232 } 233 if (status == 200 || status == 201) { 234 callback.run( {text:req.__httpReq.responseText, xml:req.__httpReq.responseXML, success:true, reqId:req.id} ); 235 } else { 236 callback.run( {text:req.__httpReq.responseText, xml:req.__httpReq.responseXML, success:false, status:status, reqId:req.id} ); 237 } 238 239 AjxRpc.freeRpcCtxt(req); 240 } 241 242 } catch (ex) { 243 if (window.AjxException) { 244 AjxException.reportScriptError(ex); 245 } 246 } 247 }; 248 249 /** 250 * @private 251 */ 252 AjxRpcRequest.__init = 253 function() { 254 if (!window.XMLHttpRequest && window.ActiveXObject) { 255 // search for the latest xmlhttp version on user's machine (IE 6) 256 var msxmlVers = ["MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"]; 257 for (var i = 0; i < msxmlVers.length; i++) { 258 try { 259 var x = new ActiveXObject(msxmlVers[i]); 260 AjxRpcRequest.__msxmlVers = msxmlVers[i]; 261 break; 262 } catch (ex) { 263 // do nothing 264 } 265 } 266 if (!AjxRpcRequest.__msxmlVers) { 267 throw new AjxException("MSXML not installed", AjxException.INTERNAL_ERROR, "AjxRpc._init"); 268 } 269 } 270 AjxRpcRequest.__inited = true; 271 }; 272 273