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