You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by dd...@apache.org on 2012/02/10 23:12:22 UTC
svn commit: r1242959 - in /shindig/trunk: config/
content/samplecontainer/examples/ features/
features/src/main/javascript/features/
features/src/main/javascript/features/container.util/
features/src/main/javascript/features/core.io/ features/src/main/...
Author: ddumont
Date: Fri Feb 10 22:12:21 2012
New Revision: 1242959
URL: http://svn.apache.org/viewvc?rev=1242959&view=rev
Log:
SHINDIG-1695 new core-gadget feature "proxied-form-post" gadgets.proxiedMultipartFormPost
Added:
shindig/trunk/content/samplecontainer/examples/ImageUploadGadget.xml (with props)
shindig/trunk/features/src/main/javascript/features/proxied-form-post/
shindig/trunk/features/src/main/javascript/features/proxied-form-post/feature.xml (with props)
shindig/trunk/features/src/main/javascript/features/proxied-form-post/post.js (with props)
shindig/trunk/features/src/main/javascript/features/proxied-form-post/taming.js (with props)
shindig/trunk/java/server/src/main/
shindig/trunk/java/server/src/main/webapp/
shindig/trunk/java/server/src/main/webapp/META-INF/
shindig/trunk/java/server/src/main/webapp/META-INF/MANIFEST.MF (with props)
Modified:
shindig/trunk/config/container.js
shindig/trunk/features/pom.xml
shindig/trunk/features/src/main/javascript/features/container.util/util.js
shindig/trunk/features/src/main/javascript/features/core.io/io.js
shindig/trunk/features/src/main/javascript/features/features.txt
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java
Modified: shindig/trunk/config/container.js
URL: http://svn.apache.org/viewvc/shindig/trunk/config/container.js?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/config/container.js (original)
+++ shindig/trunk/config/container.js Fri Feb 10 22:12:21 2012
@@ -140,6 +140,9 @@
// Enables whitelist checks
"gadgets.admin.enableGadgetWhitelist" : "false",
+// Max post size for posts through the makeRequest proxy.
+"gadgets.jsonProxyUrl.maxPostSize" : 5242880, // 5 MiB
+
// This config data will be passed down to javascript. Please
// configure your object using the feature name rather than
// the javascript name.
Added: shindig/trunk/content/samplecontainer/examples/ImageUploadGadget.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/content/samplecontainer/examples/ImageUploadGadget.xml?rev=1242959&view=auto
==============================================================================
--- shindig/trunk/content/samplecontainer/examples/ImageUploadGadget.xml (added)
+++ shindig/trunk/content/samplecontainer/examples/ImageUploadGadget.xml Fri Feb 10 22:12:21 2012
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<Module>
+ <ModulePrefs title="Proxy Tester" height="500">
+ <Require feature="dynamic-height"/>
+ <Require feature="embedded-experiences"/>
+ <Require feature="proxied-form-post"/>
+ </ModulePrefs>
+ <Content type="html"><![CDATA[
+ <script>
+ function init() {
+ gadgets.window.adjustHeight();
+ }
+
+ function doPostFile() {
+ document.getElementById("result").innerHTML = '';
+ document.getElementById("progbar").style.width = 0;
+
+ var form = document.getElementById("imageform"),
+ params = {};
+ params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
+ params[gadgets.io.RequestParameters.HEADERS] = {
+ Referer: 'http://bayimg.com/',
+ Origin: 'http://bayimg.com'
+ };
+
+ gadgets.io.proxiedMultipartFormPost(form, params, function(response) {
+ document.getElementById("progbar").style.width = "100%";
+ window.doAbort = null;
+ if (response && response.errors && response.errors.length) {
+ try {
+ document.getElementById("result").innerHTML = /<body>(.*)<\/body>/.exec(response.errors[0])[1];
+ } catch(e) {
+ document.getElementById("result").innerHTML = 'Error: ' + response.errors[0];
+ }
+ } else {
+ try {
+ var url = /<img[^>]+alt="Image"[^>]+src="([^"]+)"/.exec(response.text)[1];
+ document.getElementById("result").innerHTML = '<img src="' + url + '" onload="init();">';
+ } catch(e) {
+ document.getElementById("result").innerHTML = 'Error';
+ }
+ }
+ gadgets.window.adjustHeight();
+ },
+ function(event, abort) {
+ if (!window.doAbort)
+ window.doAbort = abort;
+ if (event && event.lengthComputable) {
+ var percent = Math.ceil((event.loaded / event.total) * 100) + "%";
+ document.getElementById("progbar").style.width = percent;
+ }
+ });
+ }
+
+ gadgets.util.registerOnLoadHandler(init);
+ </script>
+
+ <h3>Upload an image file to bayimg.com</h3>
+ <form id="imageform" action="http://upload.bayimg.com/upload">
+ <fieldset>
+ File: <input type="file" name="file"></input>
+ <input type="hidden" name="code" value="opensocial"></input>
+ <input type="hidden" name="tags" value=""></input>
+ </fieldset>
+ <input type="button" value="Upload" onClick="doPostFile();"/>
+ <input type="button" value="Abort" onClick="if (window.doAbort) doAbort();"/>
+ </form>
+
+ <div style="border:1px solid rgb(90,90,115); background-color:#ffffff; padding:0px; width:200px; height:20px;overflow:hidden;">
+ <div id="progbar" style="width:0%; height:100%; background-color:rgb(108,157,222)"></div>
+ </div>
+
+ <div id="result"></div>
+ ]]></Content>
+</Module>
\ No newline at end of file
Propchange: shindig/trunk/content/samplecontainer/examples/ImageUploadGadget.xml
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: shindig/trunk/features/pom.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/pom.xml?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/features/pom.xml (original)
+++ shindig/trunk/features/pom.xml Fri Feb 10 22:12:21 2012
@@ -170,7 +170,7 @@
<source>opensocial-base/fieldtranslations.js</source>
<source>opensocial-base/jsonactivity.js</source>
<source>opensocial-base/jsonalbum.js</source>
- <source>opensocial-base/jsonmediaitem.js</source>
+ <source>opensocial-base/jsonmediaitem.js</source>
<source>opensocial-base/jsonperson.js</source>
<source>opensocial-jsonrpc/jsonrpccontainer.js</source>
<source>osapi.base/osapi.js</source>
@@ -188,6 +188,7 @@
<source>embeddedexperiences/embedded_experiences_container.js</source>
<source>open-views/viewenhancements-container.js</source>
<source>open-views/viewenhancements.js</source>
+ <source>proxied-form-post/post.js</source>
</sources>
<testSourceDirectory>${basedir}/src/test/javascript/features</testSourceDirectory>
<testSuites>
Modified: shindig/trunk/features/src/main/javascript/features/container.util/util.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.util/util.js?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.util/util.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.util/util.js Fri Feb 10 22:12:21 2012
@@ -174,18 +174,18 @@ osapi.container.util.createIframeHtml =
// requires more code, and creating an element with it results in a click
// sound in IE (unconfirmed), setAttribute('class') may need browser-specific
// variants.
- var out = [];
- out.push('<iframe ');
+ var out = [], n = 0;
+ out[n++] = '<iframe ';
for (var key in iframeParams) {
var value = iframeParams[key];
if (typeof(value) != 'undefined') {
- out.push(key);
- out.push('="');
- out.push(value);
- out.push('" ');
+ out[n++] = key;
+ out[n++] = '="';
+ out[n++] = value;
+ out[n++] = '" ';
}
}
- out.push('></iframe>');
+ out[n++] = '></iframe>';
return out.join('');
};
Modified: shindig/trunk/features/src/main/javascript/features/core.io/io.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/core.io/io.js?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/core.io/io.js (original)
+++ shindig/trunk/features/src/main/javascript/features/core.io/io.js Fri Feb 10 22:12:21 2012
@@ -541,7 +541,12 @@ gadgets.io = function() {
ret = window.location.protocol + ret;
}
return ret;
- }
+ },
+
+ /**
+ * @private
+ */
+ processResponse_: processResponse
};
}();
Modified: shindig/trunk/features/src/main/javascript/features/features.txt
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/features.txt?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/features.txt (original)
+++ shindig/trunk/features/src/main/javascript/features/features.txt Fri Feb 10 22:12:21 2012
@@ -6,9 +6,9 @@
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -80,6 +80,7 @@ features/open-views/feature.xml
features/osapi.base/feature.xml
features/osapi/feature.xml
features/osml/feature.xml
+features/proxied-form-post/feature.xml
features/pubsub/feature.xml
features/rpc/feature.xml
features/shared-script-frame/feature.xml
Added: shindig/trunk/features/src/main/javascript/features/proxied-form-post/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/proxied-form-post/feature.xml?rev=1242959&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/proxied-form-post/feature.xml (added)
+++ shindig/trunk/features/src/main/javascript/features/proxied-form-post/feature.xml Fri Feb 10 22:12:21 2012
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations under the License.
+-->
+<feature>
+<!--
+ Required configuration:
+
+ jsonProxyUrl: A url pointing to the JSON proxy endpoint, used by
+ gadgets.io.makeRequest. All data passed to this end point will be
+ encoded inside of the POST body.
+-->
+ <name>proxied-form-post</name>
+ <dependency>core.io</dependency>
+ <dependency>security-token</dependency>
+ <dependency>taming</dependency>
+ <gadget>
+ <script src="post.js"/>
+ <script src="taming.js" caja="1"/>
+ <api>
+ <exports type="js">gadgets.io.proxiedMultipartFormPost</exports>
+ </api>
+ </gadget>
+</feature>
Propchange: shindig/trunk/features/src/main/javascript/features/proxied-form-post/feature.xml
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: shindig/trunk/features/src/main/javascript/features/proxied-form-post/post.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/proxied-form-post/post.js?rev=1242959&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/proxied-form-post/post.js (added)
+++ shindig/trunk/features/src/main/javascript/features/proxied-form-post/post.js Fri Feb 10 22:12:21 2012
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @fileoverview This provides the ability to upload a file through the shindig
+ * proxy by posting a form element.
+ */
+
+(function () {
+ var config,
+ iframe,
+ work,
+ workQ = [],
+ workTimout;
+
+ /**
+ * @param {Object} configuration Configuration settings.
+ * @private
+ */
+ function init(configuration) {
+ config = configuration['core.io'] || {};
+ }
+ gadgets.config.register('core.io', {
+ "jsonProxyUrl": gadgets.config.NonEmptyStringValidator
+ }, init);
+
+ // IE and FF3.6 only
+ function getIFrame() {
+ if (!iframe) {
+ var container = gadgets.util.createElement('div');
+ container.innerHTML =
+ '<iframe name="os-xhrframe"'
+ + ' style="position:absolute;left:1px;top:1px;height:1px;width:1px;visibility:hidden"'
+ + ' onload="gadgets.io.proxiedMultipartFormPostCB_();"></iframe>';
+ gadgets.util.getBodyElement().appendChild(iframe = container.firstChild);
+ }
+ return iframe;
+ }
+
+ gadgets.io.proxiedMultipartFormPostCB_ = function(event) {
+ if (!work) {
+ return;
+ }
+
+ try {
+ var doc = iframe.contentDocument || iframe.document,
+ data = doc.getElementsByTagName('textarea')[0].value;
+ } catch (e) {}
+ var xhrobj = {
+ readyState: 4,
+ status: data ? 200 : 500,
+ responseText: data ? data : 'Unknown error.'
+ };
+ work.form.setAttribute('action', work.url);
+
+ gadgets.io.processResponse_.call(null, work.url, work.onresult, work.params, xhrobj);
+ work = 0;
+ if (workQ.length) {
+ work = workQ.shift();
+ work.form.submit();
+ }
+ };
+
+ /**
+ * Posts a form through the proxy to a remote service.
+ *
+ * @param {Element} form The form element to be posted. This form element must
+ * include the action attribute for where you want the data to be posted.
+ * @param {Object} params The request options. Similar to gadgets.io.makeRequest
+ * @param {function} onresult The callback to process the success or failure of
+ * the post. Similar to gadgets.io.makeRequest's callback param.
+ * @param {function=} opt_onprogress The callback to call with progress updates.
+ * This callback may not be called if the browser does not
+ * support the api. Please note that this only reflects the progress of
+ * uploading to the shindig proxy and does not account for progress of
+ * the request from the shindig proxy to the remote server.
+ * This callback takes 2 arguments:
+ * {Event} event The progress event.
+ * {function} abort Function to call to abort the post.
+ * @param {FormData=} opt_formdata The FormData object to post. If provided, this
+ * object will be used as the data to post the provided form and the form
+ * element provided should contain the action attribute of where to post
+ * the form. If ommitted and the browser supports it, a FormData object
+ * will be created from the provided form element.
+ */
+ gadgets.io.proxiedMultipartFormPost = function (form, params, onresult, onprogress, formdata) {
+ params = params || {};
+
+ var auth, signOwner,
+ signViewer = signOwner = true,
+ st = shindig.auth.getSecurityToken(),
+ url = form.getAttribute('action'),
+ contentType = 'multipart/form-data',
+ headers = params['HEADERS'] || (params['HEADERS'] = {}),
+ urlParams = gadgets.util.getUrlParameters();
+
+ if (params['AUTHORIZATION'] && params['AUTHORIZATION'] !== 'NONE') {
+ auth = params['AUTHORIZATION'].toLowerCase();
+ }
+ // Include owner information?
+ if (typeof params['OWNER_SIGNED'] !== 'undefined') {
+ signOwner = params['OWNER_SIGNED'];
+ }
+ // Include viewer information?
+ if (typeof params['VIEWER_SIGNED'] !== 'undefined') {
+ signViewer = params['VIEWER_SIGNED'];
+ }
+
+ if (!url) {
+ throw new Error('Form missing action attribute.');
+ }
+ if (!st) {
+ throw new Error('Something went wrong, security token is unavailable.');
+ }
+
+ form.setAttribute('enctype', headers['Content-Type'] = contentType);
+
+ // Info that the proxy endpoint needs.
+ var query = {
+ 'MPFP': 1, // This will force an alternate route in the makeRequest proxy endpoint
+ 'url': url,
+ 'httpMethod': 'POST',
+ 'headers': gadgets.io.encodeValues(headers, false),
+ 'authz': auth || '',
+ 'st': st,
+ 'contentType': params['CONTENT_TYPE'] || 'TEXT',
+ 'signOwner': signOwner,
+ 'signViewer': signViewer,
+ // should we bypass gadget spec cache (e.g. to read OAuth provider URLs)
+ 'bypassSpecCache': gadgets.util.getUrlParameters()['nocache'] || '',
+ 'getFullHeaders': !!params['GET_FULL_HEADERS']
+ };
+
+ delete params['OAUTH_RECEIVED_CALLBACK'];
+ // OAuth goodies
+ if (auth === 'oauth' || auth === 'signed' || auth === 'oauth2') {
+ // Just copy the OAuth parameters into the req to the server
+ for (var opt in params) {
+ if (params.hasOwnProperty(opt)) {
+ if (opt.indexOf('OAUTH_') === 0 || opt === 'code') {
+ query[opt] = params[opt];
+ }
+ }
+ }
+ }
+
+ var proxyUrl = config['jsonProxyUrl'].replace('%host%', document.location.host)
+ + '?' + gadgets.io.encodeValues(query);
+
+ if (window.FormData) {
+ var xhr = new XMLHttpRequest(),
+ data = formdata || new FormData(form);
+
+ if (xhr.upload) {
+ xhr.upload.onprogress = function(event) {
+ onprogress.call(null, event, xhr.abort);
+ };
+ }
+ xhr.onreadystatechange = gadgets.util.makeClosure(
+ null, gadgets.io.processResponse_, url, onresult, params, xhr
+ );
+ xhr.open("POST", proxyUrl);
+ xhr.send(data);
+ } else {
+ // IE and FF3.6 only
+ proxyUrl += '&iframe=1';
+ form.setAttribute('action', proxyUrl);
+ form.setAttribute('target', getIFrame().name);
+ form.setAttribute('method', 'POST');
+
+ // This transport can only support 1 request at a time, so we serialize
+ // them.
+ var job = {
+ form: form,
+ onresult: onresult,
+ params: params,
+ url: url
+ };
+ if (work) {
+ workQ.push(job);
+ } else {
+ work = job;
+ form.submit();
+ }
+ }
+ };
+
+})();
\ No newline at end of file
Propchange: shindig/trunk/features/src/main/javascript/features/proxied-form-post/post.js
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: shindig/trunk/features/src/main/javascript/features/proxied-form-post/taming.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/proxied-form-post/taming.js?rev=1242959&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/proxied-form-post/taming.js (added)
+++ shindig/trunk/features/src/main/javascript/features/proxied-form-post/taming.js Fri Feb 10 22:12:21 2012
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @class
+ * Tame and expose core gadgets.io.* API to cajoled gadgets
+ */
+tamings___.push(function(imports) {
+ caja___.whitelistFuncs([
+ [gadgets.io, 'proxiedMultipartFormPost']
+ ]);
+});
Propchange: shindig/trunk/features/src/main/javascript/features/proxied-form-post/taming.js
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetException.java Fri Feb 10 22:12:21 2012
@@ -51,6 +51,7 @@ public class GadgetException extends Exc
INVALID_PARAMETER,
MISSING_PARAMETER,
UNRECOGNIZED_PARAMETER,
+ POST_TOO_LARGE,
// Interface component errors.
MISSING_FEATURE_REGISTRY,
Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpRequest.java Fri Feb 10 22:12:21 2012
@@ -26,6 +26,7 @@ import org.apache.shindig.common.uri.Uri
import org.apache.shindig.common.util.CharsetUtil;
import org.apache.shindig.config.ContainerConfig;
import org.apache.shindig.gadgets.AuthType;
+import org.apache.shindig.gadgets.admin.BasicGadgetAdminStore;
import org.apache.shindig.gadgets.oauth.OAuthArguments;
import org.apache.shindig.gadgets.oauth2.OAuth2Arguments;
@@ -40,12 +41,16 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Creates HttpRequests. A new HttpRequest should be created for every unique HttpRequest
* being constructed.
*/
public class HttpRequest {
+ private static final Logger LOG = Logger.getLogger(HttpRequest.class.getName());
+
/** Automatically added to every request so that we know that the request came from our server. */
public static final String DOS_PREVENTION_HEADER = "X-shindig-dos";
static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8";
@@ -188,20 +193,26 @@ public class HttpRequest {
* Assigns the specified body to the request, copying all input bytes.
*/
public HttpRequest setPostBody(byte[] postBody) {
- if (postBody == null) {
- this.postBody = ArrayUtils.EMPTY_BYTE_ARRAY;
- } else {
- this.postBody = new byte[postBody.length];
- System.arraycopy(postBody, 0, this.postBody, 0, postBody.length);
+ try {
+ setPostBody(postBody == null ? null : new ByteArrayInputStream(postBody));
+ } catch (IOException e){
+ if (LOG.isLoggable(Level.WARNING)) {
+ LOG.log(Level.WARNING, e.getMessage(), e); // Shouldn't ever happen.
+ }
}
return this;
}
/**
* Fills in the request body from an InputStream.
+ * @throws IOException
*/
public HttpRequest setPostBody(InputStream is) throws IOException {
- postBody = IOUtils.toByteArray(is);
+ if (postBody == null) {
+ this.postBody = ArrayUtils.EMPTY_BYTE_ARRAY;
+ } else {
+ postBody = IOUtils.toByteArray(is);
+ }
return this;
}
@@ -291,7 +302,7 @@ public class HttpRequest {
this.oauth2Arguments = oauth2Arguments;
return this;
}
-
+
/**
* @param followRedirects whether this request should automatically follow redirects.
*/
@@ -465,7 +476,7 @@ public class HttpRequest {
return oauth2Arguments;
}
-
+
/**
* @return true if redirects should be followed.
*/
@@ -516,7 +527,7 @@ public class HttpRequest {
^ Arrays.hashCode(postBody)
^ headers.hashCode();
}
-
+
@Override
public boolean equals(Object obj) {
if (obj == this) {
Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java Fri Feb 10 22:12:21 2012
@@ -19,7 +19,9 @@
package org.apache.shindig.gadgets.servlet;
import java.io.IOException;
+import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
+import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@@ -54,20 +56,20 @@ import org.apache.shindig.gadgets.rewrit
import org.apache.shindig.gadgets.rewrite.ResponseRewriterRegistry;
import org.apache.shindig.gadgets.rewrite.RewriterRegistry;
import org.apache.shindig.gadgets.rewrite.RewritingException;
-import org.apache.shindig.gadgets.uri.UriCommon;
import org.apache.shindig.gadgets.uri.UriCommon.Param;
+import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
* Handles gadgets.io.makeRequest requests.
- *
+ *
* Unlike ProxyHandler, this may perform operations such as OAuth or signed fetch.
*/
@Singleton
-public class MakeRequestHandler {
+public class MakeRequestHandler implements ContainerConfig.ConfigObserver {
// Relaxed visibility for ease of integration. Try to avoid relying on these.
public static final String UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >";
public static final String POST_DATA_PARAM = "postData";
@@ -79,6 +81,10 @@ public class MakeRequestHandler {
public static final String GET_SUMMARIES_PARAM = "getSummaries";
public static final String GET_FULL_HEADERS_PARAM = "getFullHeaders";
public static final String AUTHZ_PARAM = "authz";
+ public static final String MAX_POST_SIZE_KEY = "gadgets.jsonProxyUrl.maxPostSize";
+ public static final String MULTI_PART_FORM_POST = "MPFP";
+ public static final String MULTI_PART_FORM_POST_IFRAME = "iframe";
+ public static final int MAX_POST_SIZE_DEFAULT = 5 * 1024 * 1024; // 5 MiB
private final RequestPipeline requestPipeline;
private final ResponseRewriterRegistry contentRewriterRegistry;
@@ -86,9 +92,11 @@ public class MakeRequestHandler {
private final GadgetAdminStore gadgetAdminStore;
private final Processor processor;
private final LockedDomainService lockedDomainService;
+ private final Map<String, Integer> maxPostSizes;
@Inject
public MakeRequestHandler(
+ ContainerConfig config,
RequestPipeline requestPipeline,
@RewriterRegistry(rewriteFlow = RewriteFlow.DEFAULT) ResponseRewriterRegistry contentRewriterRegistry,
Provider<FeedProcessor> feedProcessorProvider, GadgetAdminStore gadgetAdminStore,
@@ -100,6 +108,8 @@ public class MakeRequestHandler {
this.gadgetAdminStore = gadgetAdminStore;
this.processor = processor;
this.lockedDomainService = lockedDomainService;
+ this.maxPostSizes = Maps.newConcurrentMap();
+ config.addConfigObserver(this, true);
}
/**
@@ -107,9 +117,9 @@ public class MakeRequestHandler {
*/
public void fetch(HttpServletRequest request, HttpServletResponse response)
throws GadgetException, IOException {
+
HttpRequest rcr = buildHttpRequest(request);
String container = rcr.getContainer();
-
final Uri gadgetUri = rcr.getGadget();
if (gadgetUri == null) {
throw new GadgetException(GadgetException.Code.MISSING_PARAMETER,
@@ -118,10 +128,11 @@ public class MakeRequestHandler {
Gadget gadget;
GadgetContext context = new HttpGadgetContext(request) {
+ @Override
public Uri getUrl() {
return gadgetUri;
}
-
+ @Override
public boolean getIgnoreCache() {
return getParameter("bypassSpecCache").equals("1");
}
@@ -164,20 +175,29 @@ public class MakeRequestHandler {
// Find and set the refresh interval
setResponseHeaders(request, response, results);
-
response.setStatus(HttpServletResponse.SC_OK);
- response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
- response.getWriter().write(UNPARSEABLE_CRUFT + output);
+
+ PrintWriter out = response.getWriter();
+ if ("1".equals(getParameter(request, MULTI_PART_FORM_POST_IFRAME, null))) {
+ response.setContentType("text/html");
+ out.write("<html><head></head><body><textarea>");
+ out.write(UNPARSEABLE_CRUFT);
+ out.write(output);
+ out.write("</textarea></body></html>");
+ } else {
+ response.setContentType("application/json");
+ out.write(UNPARSEABLE_CRUFT + output);
+ }
}
/**
* Generate a remote content request based on the parameters sent from the client.
- *
+ *
* @throws GadgetException
*/
protected HttpRequest buildHttpRequest(HttpServletRequest request) throws GadgetException {
- String urlStr = request.getParameter(Param.URL.getKey());
+ String urlStr = getParameter(request, Param.URL.getKey(), null);
if (urlStr == null) {
throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, Param.URL.getKey()
+ " parameter is missing.", HttpResponse.SC_BAD_REQUEST);
@@ -191,10 +211,38 @@ public class MakeRequestHandler {
+ Param.URL.getKey() + " parameter", HttpResponse.SC_BAD_REQUEST);
}
+ SecurityToken token = AuthInfoUtil.getSecurityTokenFromRequest(request);
+ String container = null;
+ Uri gadgetUri = null;
+ if ("1".equals(getParameter(request, MULTI_PART_FORM_POST, null))) {
+ // This endpoint is being used by the proxied-form-post feature.
+ // Require a token.
+ if (token == null) {
+ throw new GadgetException(GadgetException.Code.INVALID_SECURITY_TOKEN);
+ }
+ }
+
+ // If we have a token, we should use it.
+ if (token != null && !token.isAnonymous()) {
+ container = token.getContainer();
+ String appurl = token.getAppUrl();
+ if (appurl != null) {
+ gadgetUri = Uri.parse(appurl);
+ }
+ } else {
+ container = getContainer(request);
+ String gadgetUrl = getParameter(request, Param.GADGET.getKey(), null);
+ if (gadgetUrl != null) {
+ gadgetUri = Uri.parse(gadgetUrl);
+ }
+ }
+
HttpRequest req = new HttpRequest(url).setMethod(getParameter(request, METHOD_PARAM, "GET"))
- .setContainer(getContainer(request));
+ .setContainer(container).setGadget(gadgetUri);
- setPostData(request, req);
+ if ("POST".equals(req.getMethod())) {
+ setPostData(container, request, req);
+ }
String headerData = getParameter(request, HEADERS_PARAM, "");
if (headerData.length() > 0) {
@@ -215,28 +263,28 @@ public class MakeRequestHandler {
// Set the default content type for post requests when a content type is not specified
if ("POST".equals(req.getMethod()) && req.getHeader("Content-Type") == null) {
req.addHeader("Content-Type", "application/x-www-form-urlencoded");
+ } else if ("1".equals(getParameter(request, MULTI_PART_FORM_POST, null))) {
+ // We need the entire header from the original request because it comes with a boundry value we need to reuse.
+ req.addHeader("Content-Type", request.getHeader("Content-Type"));
}
- req.setIgnoreCache("1".equals(request.getParameter(Param.NO_CACHE.getKey())));
+ req.setIgnoreCache("1".equals(getParameter(request, Param.NO_CACHE.getKey(), null)));
+
- if (request.getParameter(Param.GADGET.getKey()) != null) {
- req.setGadget(Uri.parse(request.getParameter(Param.GADGET.getKey())));
- }
// If the proxy request specifies a refresh param then we want to force the min TTL for
// the retrieved entry in the cache regardless of the headers on the content when it
// is fetched from the original source.
- if (request.getParameter(Param.REFRESH.getKey()) != null) {
+ String refresh = getParameter(request, Param.REFRESH.getKey(), null);
+ if (refresh != null) {
try {
- req.setCacheTtl(Integer.parseInt(request.getParameter(Param.REFRESH.getKey())));
- } catch (NumberFormatException nfe) {
- // Ignore
- }
+ req.setCacheTtl(Integer.parseInt(refresh));
+ } catch (NumberFormatException ignore) {}
}
// Allow the rewriter to use an externally forced mime type. This is needed
// allows proper rewriting of <script src="x"/> where x is returned with
// a content type like text/html which unfortunately happens all too often
- req.setRewriteMimeType(request.getParameter(Param.REWRITE_MIME_TYPE.getKey()));
+ req.setRewriteMimeType(getParameter(request, Param.REWRITE_MIME_TYPE.getKey(), null));
// Figure out whether authentication is required
AuthType auth = AuthType.parse(getParameter(request, AUTHZ_PARAM, null));
@@ -259,18 +307,34 @@ public class MakeRequestHandler {
* Set http request post data according to servlet request. It uses header encoding if available,
* and defaulted to utf8 Override the function if different behavior is needed.
*/
- protected void setPostData(HttpServletRequest request, HttpRequest req) throws GadgetException {
+ protected void setPostData(String container, HttpServletRequest request, HttpRequest req) throws GadgetException {
+ if (maxPostSizes.get(container) < request.getContentLength()) {
+ throw new GadgetException(GadgetException.Code.POST_TOO_LARGE, "Posted data too large.",
+ HttpResponse.SC_REQUEST_ENTITY_TOO_LARGE);
+ }
+
String encoding = request.getCharacterEncoding();
if (encoding == null) {
encoding = "UTF-8";
}
try {
- req.setPostBody(getParameter(request, POST_DATA_PARAM, "").getBytes(encoding.toUpperCase()));
+ String contentType = request.getHeader("Content-Type");
+ if (contentType != null && contentType.startsWith("multipart/form-data")) {
+ // TODO: This will read the entire posted response in server memory.
+ // Is there a way to stream this even with OAUTH flows?
+ req.setPostBody(request.getInputStream());
+ } else {
+ req.setPostBody(getParameter(request, POST_DATA_PARAM, "").getBytes(encoding.toUpperCase()));
+ }
} catch (UnsupportedEncodingException e) {
// We might consider enumerating at least a small list of encodings
// that we must always honor. For now, we return SC_BAD_REQUEST since
// the encoding parameter could theoretically be anything.
throw new GadgetException(Code.HTML_PARSE_ERROR, e, HttpResponse.SC_BAD_REQUEST);
+ } catch (IOException e) {
+ // Something went wrong reading the request data.
+ // TODO: perhaps also support a max post size and enforce it by throwing and catching exceptions here.
+ throw new GadgetException(Code.INTERNAL_SERVER_ERROR, e, HttpResponse.SC_BAD_REQUEST);
}
}
@@ -281,10 +345,10 @@ public class MakeRequestHandler {
HttpResponse results) throws GadgetException {
boolean getFullHeaders = Boolean.parseBoolean(getParameter(request, GET_FULL_HEADERS_PARAM,
"false"));
- String originalUrl = request.getParameter(Param.URL.getKey());
+ String originalUrl = getParameter(request, Param.URL.getKey(), null);
String body = results.getResponseAsString();
if (body.length() > 0) {
- if ("FEED".equals(request.getParameter(CONTENT_TYPE_PARAM))) {
+ if ("FEED".equals(getParameter(request, CONTENT_TYPE_PARAM, null))) {
body = processFeed(originalUrl, request, body);
}
}
@@ -339,9 +403,9 @@ public class MakeRequestHandler {
*/
@SuppressWarnings("deprecation")
protected static String getContainer(HttpServletRequest request) {
- String container = request.getParameter(Param.CONTAINER.getKey());
+ String container = getParameter(request, Param.CONTAINER.getKey(), null);
if (container == null) {
- container = request.getParameter(Param.SYND.getKey());
+ container = getParameter(request, Param.SYND.getKey(), null);
}
return container != null ? container : ContainerConfig.DEFAULT_CONTAINER;
}
@@ -362,11 +426,11 @@ public class MakeRequestHandler {
HttpServletResponse response, HttpResponse results) throws GadgetException {
int refreshInterval = 0;
if (results.isStrictNoCache()
- || "1".equals(request.getParameter(UriCommon.Param.NO_CACHE.getKey()))) {
+ || "1".equals(getParameter(request, Param.NO_CACHE.getKey(), null))) {
refreshInterval = 0;
- } else if (request.getParameter(UriCommon.Param.REFRESH.getKey()) != null) {
+ } else if (getParameter(request, Param.REFRESH.getKey(), null) != null) {
try {
- refreshInterval = Integer.valueOf(request.getParameter(UriCommon.Param.REFRESH.getKey()));
+ refreshInterval = Integer.valueOf(getParameter(request, Param.REFRESH.getKey(), null));
} catch (NumberFormatException nfe) {
throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
"refresh parameter is not a number", HttpResponse.SC_BAD_REQUEST);
@@ -376,11 +440,37 @@ public class MakeRequestHandler {
}
HttpUtil.setCachingHeaders(response, refreshInterval, false);
- // Always set Content-Disposition header as XSS prevention mechanism.
- response.setHeader("Content-Disposition", "attachment;filename=p.txt");
+ /*
+ * The proxied-form-post feature uses this endpoint to post a form
+ * element (in order to support file upload).
+ *
+ * For cross-browser support (IE) it requires that we use a hidden iframe
+ * to post the request. Setting Content-Disposition breaks that solution.
+ * In this particular case, we will always have a security token, so we
+ * shouldn't need to be as cautious here.
+ */
+ if (!"1".equals(getParameter(request, MULTI_PART_FORM_POST, null))) {
+ // Always set Content-Disposition header as XSS prevention mechanism.
+ response.setHeader("Content-Disposition", "attachment;filename=p.txt");
+ }
if (response.getContentType() == null) {
response.setContentType("application/octet-stream");
}
}
+
+ public void containersChanged(ContainerConfig config, Collection<String> changed,
+ Collection<String> removed) {
+ for (String container : changed) {
+ Integer maxPostSize = config.getInt(container, MAX_POST_SIZE_KEY);
+ if (maxPostSize == null) {
+ maxPostSize = MAX_POST_SIZE_DEFAULT;
+ } else {
+ maxPostSizes.put(container, maxPostSize);
+ }
+ }
+ for (String container : removed) {
+ maxPostSizes.remove(container);
+ }
+ }
}
Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java Fri Feb 10 22:12:21 2012
@@ -69,7 +69,7 @@ public class MakeRequestHandlerTest exte
private static final Uri REQUEST_URL = Uri.parse("http://example.org/file");
private static final String REQUEST_BODY = "I+am+the+request+body!foo=baz%20la";
private static final String RESPONSE_BODY = "makeRequest response body";
- private static final SecurityToken DUMMY_TOKEN = new FakeGadgetToken();
+ private static final FakeGadgetToken DUMMY_TOKEN = new FakeGadgetToken();
private final GadgetAdminStore gadgetAdminStore = mock(GadgetAdminStore.class);
private ContainerConfig containerConfig;
@@ -125,8 +125,10 @@ public class MakeRequestHandlerTest exte
containerConfig = new JsonContainerConfig(config, Expressions.forTesting());
ldService = new HashLockedDomainService(containerConfig, false, new HashShaLockedDomainPrefixGenerator());
- handler = new MakeRequestHandler(pipeline, rewriterRegistry, feedProcessorProvider, gadgetAdminStore, processor, ldService);
+ handler = new MakeRequestHandler(containerConfig, pipeline, rewriterRegistry, feedProcessorProvider, gadgetAdminStore, processor, ldService);
+ DUMMY_TOKEN.setAppUrl("http://some/gadget.xml");
+ DUMMY_TOKEN.setContainer(ContainerConfig.DEFAULT_CONTAINER);
expect(request.getParameter(Param.GADGET.getKey())).andReturn("http://some/gadget.xml").anyTimes();
expect(processor.process(capture(context))).andReturn(gadget).anyTimes();
expect(gadgetAdminStore.isWhitelisted(isA(String.class), isA(String.class))).andReturn(true);
@@ -440,7 +442,10 @@ public class MakeRequestHandlerTest exte
// Doesn't actually sign since it returns the standard fetcher.
// Signing tests are in SigningFetcherTest
expectGetAndReturnBody(AuthType.SIGNED, RESPONSE_BODY);
- FakeGadgetToken authToken = new FakeGadgetToken().setUpdatedToken("updated");
+ FakeGadgetToken authToken = new FakeGadgetToken()
+ .setUpdatedToken("updated")
+ .setAppUrl(DUMMY_TOKEN.getAppUrl())
+ .setContainer(DUMMY_TOKEN.getContainer());
expect(request.getAttribute(AuthInfoUtil.Attribute.SECURITY_TOKEN.getId()))
.andReturn(authToken).atLeastOnce();
expect(request.getParameter(MakeRequestHandler.AUTHZ_PARAM))
@@ -461,7 +466,10 @@ public class MakeRequestHandlerTest exte
// Doesn't actually do oauth dance since it returns the standard fetcher.
// OAuth tests are in OAuthRequestTest
expectGetAndReturnBody(AuthType.OAUTH, RESPONSE_BODY);
- FakeGadgetToken authToken = new FakeGadgetToken().setUpdatedToken("updated");
+ FakeGadgetToken authToken = new FakeGadgetToken()
+ .setUpdatedToken("updated")
+ .setAppUrl(DUMMY_TOKEN.getAppUrl())
+ .setContainer(DUMMY_TOKEN.getContainer());
expect(request.getAttribute(AuthInfoUtil.Attribute.SECURITY_TOKEN.getId()))
.andReturn(authToken).atLeastOnce();
expect(request.getParameter(MakeRequestHandler.AUTHZ_PARAM))
Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java?rev=1242959&r1=1242958&r2=1242959&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java Fri Feb 10 22:12:21 2012
@@ -87,7 +87,7 @@ public class MakeRequestServletTest exte
Capture<GadgetContext> context = new Capture<GadgetContext>();
expect(processor.process(EasyMock.capture(context))).andReturn(gadget).anyTimes();
ldService = new HashLockedDomainService(containerConfig, false, mock(LockedDomainPrefixGenerator.class));
- handler = new MakeRequestHandler(pipeline, rewriterRegistry, feedProcessorProvider, gadgetAdminStore, processor, ldService);
+ handler = new MakeRequestHandler(containerConfig, pipeline, rewriterRegistry, feedProcessorProvider, gadgetAdminStore, processor, ldService);
servlet.setMakeRequestHandler(handler);
expect(request.getHeaderNames()).andReturn(EMPTY_ENUM).anyTimes();
Added: shindig/trunk/java/server/src/main/webapp/META-INF/MANIFEST.MF
URL: http://svn.apache.org/viewvc/shindig/trunk/java/server/src/main/webapp/META-INF/MANIFEST.MF?rev=1242959&view=auto
==============================================================================
--- shindig/trunk/java/server/src/main/webapp/META-INF/MANIFEST.MF (added)
+++ shindig/trunk/java/server/src/main/webapp/META-INF/MANIFEST.MF Fri Feb 10 22:12:21 2012
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+
Propchange: shindig/trunk/java/server/src/main/webapp/META-INF/MANIFEST.MF
------------------------------------------------------------------------------
svn:mime-type = text/plain