Skip to content

Commit 12ec5b2

Browse files
authored
Merge pull request #5717 from eclipse-ee4j/make_sure_csp_backport_passes_the_4.1_tck
Make sure CSP backport passes the 4.1 TCK
2 parents dee5ea9 + 04d82a8 commit 12ec5b2

8 files changed

Lines changed: 104 additions & 35 deletions

File tree

impl/pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,9 @@
275275
</includes>
276276
<outputFilename>#{path}/faces.js</outputFilename>
277277
<skipMerge>true</skipMerge>
278-
<closureLanguageOut>NO_TRANSPILE</closureLanguageOut>
278+
<!-- Transpile down to ES5 so the minified faces.js parses on HtmlUnit's bundled Rhino as used in 4.x TCK.
279+
Rhino's effective compatibility is ES5-ish. This config is no longer used in 5.0 branch. -->
280+
<closureLanguageOut>ECMASCRIPT5</closureLanguageOut>
279281
<closureEmitUseStrict>false</closureEmitUseStrict>
280282
</configuration>
281283
</execution>

impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import static jakarta.faces.application.ProjectStage.Development;
4343
import static jakarta.faces.application.Resource.COMPONENT_RESOURCE_KEY;
4444
import static jakarta.faces.application.StateManager.IS_BUILDING_INITIAL_STATE;
45-
import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_SERVER;
45+
import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_CLIENT;
4646
import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY;
4747
import static jakarta.faces.application.ViewHandler.DEFAULT_FACELETS_SUFFIX;
4848
import static jakarta.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME;
@@ -91,6 +91,7 @@
9191
import jakarta.faces.component.Doctype;
9292
import jakarta.faces.component.EditableValueHolder;
9393
import jakarta.faces.component.UIComponent;
94+
import jakarta.faces.component.UIForm;
9495
import jakarta.faces.component.UIPanel;
9596
import jakarta.faces.component.UIViewRoot;
9697
import jakarta.faces.component.html.HtmlDoctype;
@@ -410,7 +411,7 @@ public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOExcep
410411
*
411412
* Note if you flag a view as transient then we won't acquire the session as you are stating it does not need one.
412413
*/
413-
if (isServerStateSaving() && !viewToRender.isTransient()) {
414+
if (!viewToRender.isTransient() && extContext.getSession(false) == null && isServerStateSaving() && hasForm(ctx, viewToRender)) {
414415
getSession(ctx);
415416
}
416417

@@ -1834,11 +1835,7 @@ private void reapplyDynamicRemove(FacesContext context, ComponentStruct struct)
18341835
* @return true if we are, false otherwise.
18351836
*/
18361837
private boolean isServerStateSaving() {
1837-
if (STATE_SAVING_METHOD_SERVER.equals(webConfig.getOptionValue(StateSavingMethod))) {
1838-
return true;
1839-
}
1840-
1841-
return false;
1838+
return !STATE_SAVING_METHOD_CLIENT.equalsIgnoreCase(webConfig.getOptionValue(StateSavingMethod));
18421839
}
18431840

18441841
/**
@@ -1856,6 +1853,25 @@ private HttpSession getSession(FacesContext context) {
18561853
return null;
18571854
}
18581855

1856+
/**
1857+
* @return true if the view contains at least one {@link UIForm}, in which case state will be written and a session
1858+
* is needed under server-side state saving.
1859+
*/
1860+
private static boolean hasForm(FacesContext context, UIViewRoot viewRoot) {
1861+
if (viewRoot == null || viewRoot.getChildCount() == 0) {
1862+
return false;
1863+
}
1864+
boolean[] found = { false };
1865+
viewRoot.visitTree(VisitContext.createVisitContext(context), (visitContext, target) -> {
1866+
if (target instanceof UIForm) {
1867+
found[0] = true;
1868+
return VisitResult.COMPLETE;
1869+
}
1870+
return VisitResult.ACCEPT;
1871+
});
1872+
return found[0];
1873+
}
1874+
18591875
/**
18601876
* Gets and if needed initializes the faceletFactory
18611877
*

impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ public void processPartial(PhaseId phaseId) {
300300
if (isRenderAll()) {
301301
renderAll(ctx, viewRoot);
302302
renderState(ctx);
303+
renderEvalScripts(ctx);
303304
doFlashPostPhaseActions(ctx);
304305
writer.endDocument();
305306
return;

impl/src/main/java/com/sun/faces/renderkit/html_basic/CommandLinkRenderer.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ public void encodeEnd(FacesContext context, UIComponent component) throws IOExce
119119
writer.endElement("span");
120120
} else {
121121
writer.endElement("a");
122+
123+
String target = (String) component.getAttributes().get("target");
124+
if (target != null) {
125+
target = target.trim();
126+
} else {
127+
target = "";
128+
}
129+
Collection<ClientBehaviorContext.Parameter> params = getBehaviorParameters(component);
130+
RenderKitUtils.renderOnclickEventListener(context, component, params, target, true);
122131
}
123132
}
124133

@@ -161,16 +170,6 @@ protected void renderAsActive(FacesContext context, UIComponent command) throws
161170
// render the current value as link text.
162171
writeValue(command, writer);
163172
writer.flush();
164-
165-
String target = (String) command.getAttributes().get("target");
166-
if (target != null) {
167-
target = target.trim();
168-
} else {
169-
target = "";
170-
}
171-
172-
Collection<ClientBehaviorContext.Parameter> params = getBehaviorParameters(command);
173-
RenderKitUtils.renderOnclickEventListener(context, command, params, target, true);
174173
}
175174

176175
// --------------------------------------------------------- Private Methods

impl/src/main/java/com/sun/faces/renderkit/html_basic/HtmlBasicRenderer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.sun.faces.util.Util;
4646

4747
import jakarta.faces.component.NamingContainer;
48+
import jakarta.faces.component.UICommand;
4849
import jakarta.faces.component.UIComponent;
4950
import jakarta.faces.component.UIInput;
5051
import jakarta.faces.component.UIParameter;
@@ -579,13 +580,16 @@ protected boolean shouldWriteIdAttribute(UIComponent component) {
579580
// By default we only write the id attribute if:
580581
//
581582
// - We have a non-auto-generated id, or...
582-
// - We have client behaviors.
583+
// - We have client behaviors, or...
584+
// - The component is a UICommand (its CSP-style click listener is attached
585+
// via mojarra.ael(clientId, ...) which needs the id on the rendered element).
583586
//
584587
// We assume that if client behaviors are present, they
585588
// may need access to the id (AjaxBehavior certainly does).
586589

587590
String id;
588591
return null != (id = component.getId()) && (!id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)
592+
|| component instanceof UICommand
589593
|| component instanceof ClientBehaviorHolder && !((ClientBehaviorHolder) component).getClientBehaviors().isEmpty());
590594
}
591595

impl/src/main/resources/META-INF/resources/jakarta.faces/faces-uncompressed.js

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ if ( !( (window.faces && window.faces.specversion && window.faces.specversion >=
117117
return inputElementName in form ? form[inputElementName] : getElementByName(form,inputElementName);
118118
}
119119

120-
/**
121-
* append a new pair of parameter=value to a query string
122-
* @ignore
123-
*/
124120

125121
/**
126122
* return true if one of the dom elements contains
@@ -355,12 +351,19 @@ if ( !( (window.faces && window.faces.specversion && window.faces.specversion >=
355351
};
356352

357353
/**
358-
* Remove all the portion of code matching the script pattern from the passed string
354+
* Remove all the portion of code matching the script pattern from the passed string,
355+
* preserving scripts whose type is set to something other than text/javascript.
359356
* @param html a String containing a portion of html
360357
* @ignore
361358
*/
362359
const removeScripts = function removeScripts(html) {
363-
return html.replace(/<script[^>]*type="text\/javascript"[^>]*>([\S\s]*?)<\/script>/igm, EMPTY);
360+
return html.replace(/<script[^>]*>([\S\s]*?)<\/script>/igm, (match) => {
361+
const type = match.match(TAG_ATTRIBUTE_TYPE_REGEX);
362+
if (!!type && type[1] !== "text/javascript") {
363+
return match; // keep non-text/javascript scripts
364+
}
365+
return EMPTY;
366+
});
364367
};
365368

366369
/**
@@ -3222,17 +3225,9 @@ mojarra.l = function l(l) {
32223225
if (document.readyState === "complete") {
32233226
setTimeout(l);
32243227
}
3225-
else if (window.addEventListener) {
3226-
window.addEventListener("load", l, false);
3227-
}
3228-
else if (typeof window.onload === "function") {
3229-
const oldListener = window.onload;
3230-
window.onload = function() { oldListener(); l(); };
3231-
}
32323228
else {
3233-
window.onload = l;
3229+
window.addEventListener("load", l);
32343230
}
3235-
32363231
};
32373232

32383233
/**

impl/src/test/ts/spec/faces.ajax.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,58 @@ describe("faces.ajax.response", () => {
11251125
expect(created!.value).toBe("sharedState");
11261126
otherForm.remove();
11271127
});
1128+
1129+
test("update jakarta.faces.Resource injects new <link> stylesheet into head", () => {
1130+
const beforeLinks = document.head.querySelectorAll("link").length;
1131+
ajax().request(button, null, { render: "testForm" });
1132+
const href = "/test/jakarta.faces.resource/issue4345.css.xhtml?firstParam=1&amp;secondParam=2";
1133+
const xml = successResponse(
1134+
`<update id="jakarta.faces.Resource"><![CDATA[<link type="text/css" rel="stylesheet" href="${href}" />]]></update>`
1135+
);
1136+
lastXHR().respond(200, "", xml);
1137+
1138+
const links = document.head.querySelectorAll("link");
1139+
expect(links.length).toBe(beforeLinks + 1);
1140+
const injected = links[links.length - 1];
1141+
expect(injected.getAttribute("rel")).toBe("stylesheet");
1142+
// href is unescaped before being assigned to the DOM property
1143+
expect(injected.href).toContain("issue4345.css.xhtml?firstParam=1&secondParam=2");
1144+
injected.remove();
1145+
});
1146+
1147+
test("update jakarta.faces.Resource skips <link> already present in head", () => {
1148+
const href = "/test/jakarta.faces.resource/already-loaded.css.xhtml";
1149+
const existing = document.createElement("link");
1150+
existing.setAttribute("type", "text/css");
1151+
existing.setAttribute("rel", "stylesheet");
1152+
existing.setAttribute("href", href);
1153+
document.head.appendChild(existing);
1154+
1155+
const beforeLinks = document.head.querySelectorAll("link").length;
1156+
ajax().request(button, null, { render: "testForm" });
1157+
const xml = successResponse(
1158+
`<update id="jakarta.faces.Resource"><![CDATA[<link type="text/css" rel="stylesheet" href="${href}" />]]></update>`
1159+
);
1160+
lastXHR().respond(200, "", xml);
1161+
1162+
expect(document.head.querySelectorAll("link").length).toBe(beforeLinks);
1163+
existing.remove();
1164+
});
1165+
1166+
test("update jakarta.faces.Resource handles multiple <link> tags without throwing", () => {
1167+
const beforeLinks = document.head.querySelectorAll("link").length;
1168+
ajax().request(button, null, { render: "testForm" });
1169+
const xml = successResponse(
1170+
'<update id="jakarta.faces.Resource"><![CDATA[' +
1171+
'<link type="text/css" rel="stylesheet" href="/test/a.css" />' +
1172+
'<link type="text/css" rel="stylesheet" href="/test/b.css" />' +
1173+
']]></update>'
1174+
);
1175+
expect(() => lastXHR().respond(200, "", xml)).not.toThrow();
1176+
1177+
expect(document.head.querySelectorAll("link").length).toBe(beforeLinks + 2);
1178+
document.head.querySelectorAll('link[href^="/test/"]').forEach(el => el.remove());
1179+
});
11281180
});
11291181

11301182
// ---- HTTP error codes ----

impl/src/test/ts/spec/mojarra.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ describe("mojarra.l", () => {
688688

689689
(moj().l as Function)(callback);
690690

691-
expect(spy).toHaveBeenCalledWith("load", callback, false);
691+
expect(spy).toHaveBeenCalledWith("load", callback);
692692

693693
spy.mockRestore();
694694
if (originalReadyState) {

0 commit comments

Comments
 (0)