1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 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, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Resets the AjxPost object.
 27  * @constructor
 28  * @class
 29  * This singleton class makes an HTTP POST to the server and receives the response, passing returned data
 30  * to a callback. This class is used to upload files from the client browser to the server using the file
 31  * upload feature of POST.
 32  *
 33  * @param	{string}	iframeId		the iframe ID
 34  * 
 35  * @author Conrad Damon
 36  * 
 37  * @private
 38  */
 39 AjxPost = function(iframeId) {
 40 	this._callback = null;
 41 	this._iframeId = iframeId;
 42 }
 43 
 44 
 45 // Globals
 46 
 47 AjxPost._reqIds = 0;
 48 AjxPost._outStandingRequests = new Object();
 49 
 50 
 51 // Consts 
 52 
 53 // Common HttpServletResponse error codes
 54 // - see full list: http://java.sun.com/products/servlet/2.2/javadoc/javax/servlet/http/HttpServletResponse.html
 55 AjxPost.SC_CONTINUE					= 100;
 56 AjxPost.SC_OK						= 200;
 57 AjxPost.SC_ACCEPTED 				= 202;
 58 AjxPost.SC_NO_CONTENT 				= 204;
 59 AjxPost.SC_BAD_REQUEST				= 400;
 60 AjxPost.SC_UNAUTHORIZED				= 401;
 61 AjxPost.SC_REQUEST_TIMEOUT			= 408;
 62 AjxPost.SC_CONFLICT					= 409;
 63 AjxPost.SC_REQUEST_ENTITY_TOO_LARGE = 413;
 64 AjxPost.SC_INTERNAL_SERVER_ERROR	= 500;
 65 AjxPost.SC_BAD_GATEWAY 				= 502;
 66 AjxPost.SC_SERVICE_UNAVAILABLE		= 503;
 67 
 68 
 69 // Public methods
 70 
 71 /**
 72 * Submits the form.
 73 *
 74 * @param callback		function to return to after the HTTP response is received
 75 * @param formId			DOM ID of the form
 76 */
 77 AjxPost.prototype.execute =
 78 function(callback, form, optionalTimeout) {
 79 	// bug fix #7361
 80 	var tags = form.getElementsByTagName("input");
 81 	var inputs = new Array();
 82 	for (var i = 0; i < tags.length; i++) {
 83 		var tag = tags[i];
 84 		if (tag.type == "file") {
 85 			inputs.push(tag);
 86 			continue;
 87 		}
 88 		// clean up form from previous posts
 89 		if (tag.name && tag.name.match(/^filename\d+$/)) {
 90 			tag.parentNode.removeChild(tag);
 91 			i--; // list is live, so stay on same index
 92 			continue;
 93 		}
 94 	}
 95 	if (window.csrfToken) {
 96 		this._addHiddenField(inputs[0], "csrfToken", window.csrfToken);
 97 	}
 98     this._addHiddenFileNames(inputs);
 99 
100 	form.target = this._iframeId;
101 	this._callback = callback;
102 	var req = new AjxPostRequest(form);
103 	var failureAction = new AjxTimedAction(this, this._onFailure, [req.id]);
104 	var timeout = optionalTimeout? optionalTimeout: 5000;
105 	AjxPost._outStandingRequests[req.id] = req;
106 	try {
107 		req.send(failureAction, timeout);
108 	} catch (ex) {
109 		if (AjxEnv.isIE) {
110 			if (ex.number == -2147024891) { // 0x80070005: E_ACCESSDENIED (Couldn't open file)
111 				throw new AjxException(ZmMsg.uploadErrorAccessDenied, ex.number);
112 			}
113 		}
114 		throw ex;
115 	}
116 };
117 
118 AjxPost.prototype._addHiddenFileNames =
119 function(inputs){
120     var m = 0;
121     for (var i = 0; i < inputs.length; i++) {
122         var fileInput = inputs[i];
123         if(fileInput.files && fileInput.files.length > 1){
124             var files = fileInput.files, fileStr=[];
125             for(var j=0; j<files.length; j++){
126                var f = files[j];
127                fileStr.push(f.name || f.fileName);
128             }
129             this._addHiddenFileName(inputs[i], fileStr.join('\n'), ++m);
130         }else{
131             this._addHiddenFileName(inputs[i], inputs[i].value, ++m);
132         }
133     }
134 
135 };
136 
137 AjxPost.prototype._addHiddenFileName =
138 function(inputField, fileName, index){
139 	this._addHiddenField(inputField, "filename" + (index), fileName);
140 };
141 AjxPost.prototype._addHiddenField = function(referenceElement, fieldName, fieldValue){
142 	var hidden   = document.createElement("input");
143 	hidden.type  = "hidden";
144 	hidden.name  = fieldName;
145 	hidden.value = fieldValue;
146 	referenceElement.parentNode.insertBefore(hidden, referenceElement);
147 };
148 
149 
150 // Private methods
151 
152 AjxPost.prototype._onFailure =
153 function (reqId){
154 	var req = AjxPost._outStandingRequests[reqId];
155 	req.cancel();
156 	delete AjxPost._outStandingRequests[reqId];
157 	if (this._callback) {
158 		this._callback.run([404]);
159 		this._callback = null;
160 	}
161 };
162 
163 
164 
165 /**
166 * Processes the HTTP response from the form post. The server needs to make sure this function is
167 * called and passed the appropriate args. Something like the following should do the trick:
168 * <code>
169 *        out.println("<html><head></head><body onload=\"window.parent._uploadManager.loaded(" + results +");\"></body></html>");
170 * </code>
171 *
172 * @param status		an HTTP status
173 * @param id			the id for any attachments that were uploaded
174 */
175 AjxPost.prototype.loaded =
176 function(status, reqId, id) {
177 	//alert(document.getElementById(this._iframeId).contentWindow.document.documentElement.innerHTML);
178 	var req = AjxPost._outStandingRequests[reqId];
179 	if (req && !req.hasBeenCancelled()) {
180 		req.cancelTimeout();
181 	}
182 	delete AjxPost._outStandingRequests[reqId];
183 	if (this._callback) {
184 		this._callback.run(status, id);
185 		this._callback = null;
186 	}
187 };
188 
189 /**
190  * @class
191  * 
192  * @private
193  */
194 AjxPostRequest = function(form) {
195 	this.id = AjxPost._reqIds++;
196 	this._cancelled = false;
197 	this._form = form;
198 	var inp = form.elements.namedItem("requestId");
199 	if (!inp) {
200 		inp = form.ownerDocument.createElement('input');
201 		inp.type = "hidden";
202 		inp.name = "requestId";
203 	}
204 	inp.value = this.id;
205 	form.appendChild(inp);
206 };
207 
208 AjxPostRequest.prototype.send =
209 function(failureAction, timeout) {
210 	this._form.submit();
211 };
212 
213 AjxPostRequest.prototype.hasBeenCancelled =
214 function() {
215 	return this._cancelled;
216 };
217 
218 AjxPostRequest.prototype.cancelTimeout =
219 function() {
220 	AjxTimedAction.cancelAction(this._timeoutId);
221 };
222 
223 AjxPostRequest.prototype.cancel =
224 function() {
225 	this._cancelled = true;
226 };
227