Skip to content

Commit 6b153f0

Browse files
author
zhourenjian
committed
Support subdomain cross site script query in Simple Pipe.
In the past, setup pipe to sub.main.com from main.com will result in lots of <SCRIPT> loading, and generate lots of "loading-and-loaded" UI changes in browser. And these changes will result in flashing UI experience. And now, such subdomain pipes will be carefully dealt, using permanent <IFRAME> connection or XMLHttpRequest instead. So no flashing "loading" UI.
1 parent bf5c29a commit 6b153f0

File tree

3 files changed

+255
-25
lines changed

3 files changed

+255
-25
lines changed

sources/net.sf.j2s.ajax/ajaxpipe/net/sf/j2s/ajax/SimplePipeHttpServlet.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
7979
if (type == null) {
8080
type = SimplePipeRequest.PIPE_TYPE_CONTINUUM;
8181
}
82-
doPipe(resp, key, type);
82+
String domain = req.getParameter(SimplePipeRequest.FORM_PIPE_DOMAIN);
83+
doPipe(resp, key, type, domain);
8384
}
8485

8586
/**
@@ -101,7 +102,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
101102
* type = notify
102103
* Notify that client (browser) still keeps the pipe connection.
103104
*/
104-
protected void doPipe(final HttpServletResponse resp, String key, String type)
105+
protected void doPipe(final HttpServletResponse resp, String key, String type, String domain)
105106
throws IOException {
106107
PrintWriter writer = null;
107108
resp.setHeader("Pragma", "no-cache");
@@ -122,6 +123,24 @@ protected void doPipe(final HttpServletResponse resp, String key, String type)
122123
writer.write("\");");
123124
return;
124125
}
126+
if (SimplePipeRequest.PIPE_TYPE_SUBDOMAIN_QUERY.equals(type)) { // subdomain query
127+
resp.setContentType("text/html; charset=utf-8");
128+
writer = resp.getWriter();
129+
StringBuffer buffer = new StringBuffer();
130+
buffer.append("<html><head><title></title></head><body>\r\n");
131+
buffer.append("<script type=\"text/javascript\">");
132+
buffer.append("p = new Object ();\r\n");
133+
buffer.append("p.originalDomain = document.domain;\r\n");
134+
buffer.append("document.domain = \"" + domain + "\";\r\n");
135+
buffer.append("p.key = \"" + key + "\";\r\n");
136+
buffer.append("var spr = window.parent.net.sf.j2s.ajax.SimplePipeRequest;\r\n");
137+
buffer.append("eval (\"(\" + spr.subdomainInit + \") (p);\");\r\n");
138+
buffer.append("eval (\"((\" + spr.subdomainLoopQuery + \") (p)) ();\");\r\n");
139+
buffer.append("</script>\r\n");
140+
buffer.append("</body></html>\r\n");
141+
writer.write(buffer.toString());
142+
return;
143+
}
125144
if (SimplePipeRequest.PIPE_TYPE_CONTINUUM.equals(type)) {
126145
resp.setHeader("Transfer-Encoding", "chunked");
127146
}
@@ -131,6 +150,9 @@ protected void doPipe(final HttpServletResponse resp, String key, String type)
131150
StringBuffer buffer = new StringBuffer();
132151
buffer.append("<html><head><title></title></head><body>\r\n");
133152
buffer.append("<script type=\"text/javascript\">");
153+
if (domain != null) {
154+
buffer.append("document.domain = \"" + domain + "\";");
155+
}
134156
buffer.append("function $ (s) { if (window.parent) window.parent.net.sf.j2s.ajax.SimplePipeRequest.parseReceived (s); }");
135157
buffer.append("</script>\r\n");
136158
writer.write(buffer.toString());

sources/net.sf.j2s.ajax/ajaxpipe/net/sf/j2s/ajax/SimplePipeRequest.java

Lines changed: 217 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public class SimplePipeRequest extends SimpleRPCRequest {
4949
*/
5050
protected static final String PIPE_TYPE_QUERY = "q"; // "query";
5151

52+
/**
53+
* Type of pipe request: subdomain-query
54+
*/
55+
protected static final String PIPE_TYPE_SUBDOMAIN_QUERY = "u"; // "subdomain-query";
56+
5257
/**
5358
* Type of pipe request: notify
5459
*/
@@ -80,6 +85,11 @@ public class SimplePipeRequest extends SimpleRPCRequest {
8085
*/
8186
protected static final String FORM_PIPE_TYPE = "t"; // "pipetype";
8287

88+
/**
89+
* Query key for pipe: pipetype
90+
*/
91+
protected static final String FORM_PIPE_DOMAIN = "d"; // "pipedomain";
92+
8393
/**
8494
* Query key for pipe: pipernd
8595
*/
@@ -464,6 +474,24 @@ static void pipeScript(SimplePipeRunnable runnable) { // xss
464474
// only for JavaScript
465475
}
466476

477+
/**
478+
* @param runnable
479+
* @param domain
480+
* @j2sNative
481+
var ifr = document.createElement ("IFRAME");
482+
ifr.style.display = "none";
483+
var pipeKey = runnable.pipeKey;
484+
var spr = net.sf.j2s.ajax.SimplePipeRequest;
485+
var url = runnable.getPipeURL();
486+
ifr.src = url + (url.indexOf('?') != -1 ? "&" : "?")
487+
+ spr.constructRequest(pipeKey, spr.PIPE_TYPE_SUBDOMAIN_QUERY, true)
488+
+ "&" + spr.FORM_PIPE_DOMAIN + "=" + domain;
489+
document.body.appendChild (ifr);
490+
*/
491+
static void pipeSubdomainQuery(SimplePipeRunnable runnable, String domain) {
492+
// only for JavaScript
493+
}
494+
467495
static void pipeNotify(SimplePipeRunnable runnable) { // notifier
468496
String url = runnable.getPipeURL();
469497
loadPipeScript(url + (url.indexOf('?') != -1 ? "&" : "?")
@@ -504,25 +532,7 @@ public void onLoaded() {
504532
* @j2sNative
505533
* if (window == null || window["net"] == null) return;
506534
*/ {}
507-
String responseText = pipeRequest.getResponseText();
508-
if (responseText != null) {
509-
String retStr = parseReceived(responseText);
510-
if (retStr != null && retStr.length() > 0) {
511-
String destroyedKey = PIPE_STATUS_DESTROYED;
512-
if (retStr.indexOf(destroyedKey) == 0) {
513-
int beginIndex = destroyedKey.length() + 1;
514-
String pipeKeyStr = retStr.substring(beginIndex, beginIndex + PIPE_KEY_LENGTH);
515-
SimplePipeRunnable pipe = SimplePipeHelper.getPipe(pipeKeyStr);
516-
if (pipe != null) {
517-
pipe.pipeAlive = false;
518-
pipe.pipeClosed();
519-
SimplePipeHelper.removePipe(pipeKeyStr);
520-
}
521-
//SimplePipeHelper.removePipe(pipeKeyStr);
522-
}
523-
}
524-
}
525-
535+
onPipeDataReceived(pipeRequest.getResponseText());
526536
/**
527537
* @j2sNative
528538
* net.sf.j2s.ajax.SimplePipeRequest.lastQueryReceived = true;
@@ -541,6 +551,26 @@ public void onLoaded() {
541551
sendRequest(pipeRequest, pipeMethod, pipeURL, pipeRequestData, async);
542552
}
543553

554+
static void onPipeDataReceived(String responseText) {
555+
if (responseText != null) {
556+
String retStr = parseReceived(responseText);
557+
if (retStr != null && retStr.length() > 0) {
558+
String destroyedKey = PIPE_STATUS_DESTROYED;
559+
if (retStr.indexOf(destroyedKey) == 0) {
560+
int beginIndex = destroyedKey.length() + 1;
561+
String pipeKeyStr = retStr.substring(beginIndex, beginIndex + PIPE_KEY_LENGTH);
562+
SimplePipeRunnable pipe = SimplePipeHelper.getPipe(pipeKeyStr);
563+
if (pipe != null) {
564+
pipe.pipeAlive = false;
565+
pipe.pipeClosed();
566+
SimplePipeHelper.removePipe(pipeKeyStr);
567+
}
568+
//SimplePipeHelper.removePipe(pipeKeyStr);
569+
}
570+
}
571+
}
572+
}
573+
544574
/**
545575
* Create pipe connection for the SimplePipeRunnable.
546576
* In Java, customized HttpRequest object is used to create Comet connection.
@@ -559,8 +589,11 @@ public void onLoaded() {
559589
var pipeKey = runnable.pipeKey;
560590
var spr = net.sf.j2s.ajax.SimplePipeRequest;
561591
var url = runnable.getPipeURL();
592+
var subdomain = arguments[1];
562593
ifr.src = url + (url.indexOf('?') != -1 ? "&" : "?")
563-
+ spr.constructRequest(pipeKey, spr.PIPE_TYPE_SCRIPT, true);
594+
+ spr.constructRequest(pipeKey, spr.PIPE_TYPE_SCRIPT, true)
595+
+ (subdomain == null ? ""
596+
: "&" + spr.FORM_PIPE_DOMAIN + "=" + subdomain);
564597
document.body.appendChild (ifr);
565598
var threadFun = function (pipeFun, key) {
566599
return function () {
@@ -762,12 +795,28 @@ static void ajaxPipe(final SimplePipeRunnable runnable) {
762795
* Here in JavaScript mode, try to detect whether it's in cross site
763796
* script mode or not. In XSS mode, <SCRIPT> is used to make requests.
764797
*/
765-
boolean isXSS = isXSSMode(runnable.getPipeURL());
798+
String pipeURL = runnable.getPipeURL();
799+
boolean isXSS = isXSSMode(pipeURL);
800+
boolean isSubdomain = false;
801+
if (isXSS) {
802+
isSubdomain = isSubdomain(pipeURL);
803+
}
766804

767-
if (!isXSS && pipeMode == MODE_PIPE_CONTINUUM)
805+
if ((!isXSS || isSubdomain) && pipeMode == MODE_PIPE_CONTINUUM)
768806
/**
769807
* @j2sNative
770-
* net.sf.j2s.ajax.SimplePipeRequest.pipeContinuum (runnable);
808+
* var subdomain = null;
809+
* if (isSubdomain) {
810+
* subdomain = window.location.host;
811+
* if (subdomain != null) {
812+
* var idx = subdomain.indexOf (":");
813+
* if (idx != -1) {
814+
* subdomain = subdomain.substring (0, idx);
815+
* }
816+
* document.domain = subdomain; // set owner iframe's domain
817+
* }
818+
* }
819+
* net.sf.j2s.ajax.SimplePipeRequest.pipeContinuum (runnable, subdomain);
771820
*/
772821
{
773822
//pipeQuery(runnable, "continuum");
@@ -779,6 +828,20 @@ public void run() {
779828
} else
780829
/**
781830
* @j2sNative
831+
if (isXSS && isSubdomain
832+
&& net.sf.j2s.ajax.SimplePipeRequest.isSubdomainXSSSupported ()) {
833+
var subdomain = null;
834+
subdomain = window.location.host;
835+
if (subdomain != null) {
836+
var idx = subdomain.indexOf (":");
837+
if (idx != -1) {
838+
subdomain = subdomain.substring (0, idx);
839+
}
840+
document.domain = subdomain; // set owner iframe's domain
841+
}
842+
net.sf.j2s.ajax.SimplePipeRequest.pipeSubdomainQuery (runnable, subdomain);
843+
return;
844+
}
782845
var spr = net.sf.j2s.ajax.SimplePipeRequest;
783846
spr.lastQueryReceived = true;
784847
if (spr.queryTimeoutHandle != null) {
@@ -817,4 +880,135 @@ public void run() {
817880
}
818881
}
819882

883+
/**
884+
* For early version of Firefox (<1.5) and Opera (<9.6), subdomain XSS
885+
* query may be unsupported.
886+
*
887+
* @return whether subdomain XSS is supported or not.
888+
*
889+
* @j2sNative
890+
var dua = navigator.userAgent;
891+
var dav = navigator.appVersion;
892+
if (dua.indexOf("Opera") != -1 && parseFloat (dav) < 9.6) {
893+
return false;
894+
}
895+
if (dua.indexOf("Firefox") != -1 && parseFloat (dav) < 1.5) {
896+
return false;
897+
}
898+
if (dua.indexOf("MSIE") != -1 && parseFloat (dav) < 6.0) {
899+
return false;
900+
}
901+
return true;
902+
*/
903+
static boolean isSubdomainXSSSupported() {
904+
return true;
905+
}
906+
907+
/**
908+
*
909+
* @param p
910+
* @j2sNative
911+
p.initParameters = function () {
912+
this.parentDomain = document.domain;
913+
this.pipeQueryInterval = 1000;
914+
this.lastQueryReceived = true;
915+
this.runnable = null;
916+
var oThis = this;
917+
with (window.parent) {
918+
var sph = net.sf.j2s.ajax.SimplePipeHelper;
919+
var spr = net.sf.j2s.ajax.SimplePipeRequest;
920+
this.runnable = sph.getPipe(this.key);
921+
this.pipeQueryInterval = spr.getQueryInterval ();
922+
}
923+
};
924+
p.initHttpRequest = function () {
925+
this.xhrHandle = null;
926+
if (window.XMLHttpRequest) {
927+
this.xhrHandle = new XMLHttpRequest();
928+
} else {
929+
try {
930+
this.xhrHandle = new ActiveXObject("Msxml2.XMLHTTP");
931+
} catch (e) {
932+
this.xhrHandle = new ActiveXObject("Microsoft.XMLHTTP");
933+
}
934+
}
935+
var oThis = this;
936+
this.xhrHandle.onreadystatechange = function () {
937+
var state = oThis.xhrHandle.readyState;
938+
if (state == 4) {
939+
var pipeData = oThis.xhrHandle.responseText;
940+
oThis.xhrHandle.onreadystatechange = function () {};
941+
oThis.xhrHandle = null;
942+
document.domain = oThis.parentDomain;
943+
oThis.lastQueryReceived = true;
944+
with (window.parent) {
945+
var sph = net.sf.j2s.ajax.SimplePipeHelper;
946+
var spr = net.sf.j2s.ajax.SimplePipeRequest;
947+
spr.onPipeDataReceived (pipeData);
948+
oThis.runnable = sph.getPipe (oThis.key);
949+
}
950+
}
951+
};
952+
};
953+
p.pipeXHRQuery = function (request, method, url, data) {
954+
if ("GET" == method.toUpperCase ()) {
955+
request.open (method, url + (url.indexOf ('?') != -1 ? "&" : "?") + data, true, null, null);
956+
data = null;
957+
} else {
958+
request.open (method, url, true, null, null);
959+
}
960+
request.setRequestHeader ("User-Agent",
961+
"Java2Script-Pacemaker/2.0.0 (+http://j2s.sourceforge.net)");
962+
if (method != null && method.toLowerCase () == "post") {
963+
request.setRequestHeader ("Content-type",
964+
"application/x-www-form-urlencoded");
965+
if (request.overrideMimeType) {
966+
request.setRequestHeader ("Connection", "close");
967+
}
968+
}
969+
request.send(data);
970+
};
971+
p.initParameters ();
972+
*/
973+
static void subdomainInit(Object p) {
974+
// for JavaScript only
975+
}
976+
977+
/**
978+
*
979+
* @param p
980+
* @j2sNative
981+
return function () {
982+
if (p.runnable != null) {
983+
if (p.lastQueryReceived) {
984+
p.lastQueryReceived = false;
985+
var method = null;
986+
var url = null;
987+
var data = null;
988+
with (window.parent) {
989+
method = p.runnable.getPipeMethod ();
990+
url = p.runnable.getPipeURL ();
991+
var spr = net.sf.j2s.ajax.SimplePipeRequest;
992+
data = spr.constructRequest(p.key, spr.PIPE_TYPE_QUERY, true);
993+
}
994+
try {
995+
document.domain = p.originalDomain;
996+
} catch (e) {};
997+
p.initHttpRequest ();
998+
try {
999+
p.pipeXHRQuery (p.xhrHandle, method, url, data);
1000+
} catch (e) {
1001+
p.xhrHandle.onreadystatechange = function () {};
1002+
p.xhrHandle = null;
1003+
document.domain = p.parentDomain;
1004+
p.lastQueryReceived = true;
1005+
}
1006+
}
1007+
window.setTimeout (arguments.callee, p.pipeQueryInterval);
1008+
}
1009+
};
1010+
*/
1011+
static void subdomainLoopQuery(Object p) {
1012+
// for JavaScript only
1013+
}
8201014
}

sources/net.sf.j2s.ajax/ajaxrpc/net/sf/j2s/ajax/SimpleRPCRequest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ protected static String adjustRequestURL(String method, String url, String seria
197197
if (idx4 != -1) {
198198
locHost = locHost.substring (0, idx4);
199199
}
200+
if (arguments.length == 2) { // check subdomain
201+
return host.indexOf ("." + locHost) != -1 && locPort == port
202+
&& loc.protocol == protocol && loc.protocol != "file:";
203+
}
200204
return (locHost != host || locPort != port
201205
|| loc.protocol != protocol || loc.protocol == "file:");
202206
}
@@ -206,6 +210,16 @@ protected static boolean isXSSMode(String url) {
206210
return false;
207211
}
208212

213+
/**
214+
* Check that whether it is a subdomain of current location.
215+
* @param url
216+
* @return
217+
* @j2sNative
218+
* return net.sf.j2s.ajax.SimpleRPCRequest.isXSSMode(url, true);
219+
*/
220+
protected static boolean isSubdomain(String url) {
221+
return false;
222+
}
209223
/**
210224
* Check cross site script. Only make senses for JavaScript.
211225
*

0 commit comments

Comments
 (0)