2026-05-17 11:34:08 -05:00
|
|
|
//! Minimal `javax.servlet` and `jakarta.servlet` stubs for Java harnesses.
|
|
|
|
|
//!
|
|
|
|
|
//! Many real-world Java codebases (OWASP Benchmark, Spring servlet adapters,
|
|
|
|
|
//! legacy webapps) carry `import javax.servlet.http.HttpServletRequest;` or
|
|
|
|
|
//! the `jakarta.servlet` counterpart in source files the dynamic verifier
|
|
|
|
|
//! wants to compile. Without these symbols on the classpath, `javac` emits
|
|
|
|
|
//! `error: package javax.servlet does not exist` and the verifier reports
|
|
|
|
|
//! `BuildFailed` before any sink probe runs.
|
|
|
|
|
//!
|
|
|
|
|
//! The stubs here cover the surface the harness actually needs to link:
|
|
|
|
|
//! a no-arg-constructible `HttpServletRequest` whose `setParameter` /
|
|
|
|
|
//! `setMethod` / `setBody` setters slot into the reflective adapter in
|
|
|
|
|
//! `SERVLET_HELPER`, plus the small set of getters that OWASP Benchmark
|
|
|
|
|
//! fixtures call (`getCookies`, `getHeader`, `getInputStream`, `getReader`,
|
|
|
|
|
//! `getParameter`, `getParameterMap`, `getParameterNames`,
|
|
|
|
|
//! `getParameterValues`, `getQueryString`, `getSession`). Methods return
|
|
|
|
|
//! null / empty defaults; runtime correctness past compilation is the job
|
|
|
|
|
//! of the spec-derivation fallback paths, not the build-time stubs.
|
|
|
|
|
//!
|
|
|
|
|
//! The bundle ships both `javax.servlet` and `jakarta.servlet` so source
|
|
|
|
|
//! files predating the EE 9 rename and source files using the new
|
|
|
|
|
//! namespace both link. Each stub is generated from the same template via
|
|
|
|
|
//! [`make_servlet_stubs`] so the two trees stay in sync.
|
|
|
|
|
|
|
|
|
|
/// Stub bundle for the servlet-shape Java harnesses.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `(workdir_relative_path, file_content)` pairs ready to drop into
|
|
|
|
|
/// [`crate::dynamic::lang::HarnessSource::extra_files`]. Subdirectories in
|
|
|
|
|
/// the path are created by the harness builder; the bundled files live
|
|
|
|
|
/// under `javax/servlet/...` and `jakarta/servlet/...` so `javac -d <out>`
|
|
|
|
|
/// drops the resulting `.class` files into matching package directories
|
|
|
|
|
/// and the entry source's `import javax.servlet.http.HttpServletRequest;`
|
|
|
|
|
/// resolves at compile time.
|
|
|
|
|
pub fn servlet_stub_files() -> Vec<(String, String)> {
|
|
|
|
|
let mut out = make_servlet_stubs("javax.servlet");
|
|
|
|
|
out.extend(make_servlet_stubs("jakarta.servlet"));
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Render the nine-file stub set under the given package prefix
|
|
|
|
|
/// (`javax.servlet` or `jakarta.servlet`).
|
|
|
|
|
fn make_servlet_stubs(pkg: &str) -> Vec<(String, String)> {
|
|
|
|
|
let pkg_path = pkg.replace('.', "/");
|
|
|
|
|
let http = format!("{pkg}.http");
|
|
|
|
|
let http_path = format!("{pkg_path}/http");
|
|
|
|
|
vec![
|
|
|
|
|
(
|
|
|
|
|
format!("{pkg_path}/ServletException.java"),
|
|
|
|
|
servlet_exception(pkg),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("{pkg_path}/ServletInputStream.java"),
|
|
|
|
|
servlet_input_stream(pkg),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("{pkg_path}/RequestDispatcher.java"),
|
|
|
|
|
request_dispatcher(pkg, &http),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("{pkg_path}/annotation/WebServlet.java"),
|
|
|
|
|
web_servlet(pkg),
|
|
|
|
|
),
|
|
|
|
|
(format!("{http_path}/HttpServlet.java"), http_servlet(pkg)),
|
|
|
|
|
(
|
|
|
|
|
format!("{http_path}/HttpServletRequest.java"),
|
|
|
|
|
http_servlet_request(pkg, &http),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
format!("{http_path}/HttpServletResponse.java"),
|
|
|
|
|
http_servlet_response(&http),
|
|
|
|
|
),
|
2026-05-21 14:35:42 -05:00
|
|
|
(format!("{http_path}/HttpSession.java"), http_session(&http)),
|
2026-05-17 11:34:08 -05:00
|
|
|
(format!("{http_path}/Cookie.java"), cookie(&http)),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn servlet_exception(pkg: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {pkg};
|
|
|
|
|
public class ServletException extends Exception {{
|
|
|
|
|
public ServletException() {{ super(); }}
|
|
|
|
|
public ServletException(String msg) {{ super(msg); }}
|
|
|
|
|
public ServletException(String msg, Throwable cause) {{ super(msg, cause); }}
|
|
|
|
|
public ServletException(Throwable cause) {{ super(cause); }}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn servlet_input_stream(pkg: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {pkg};
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
public abstract class ServletInputStream extends InputStream {{
|
|
|
|
|
protected ServletInputStream() {{}}
|
|
|
|
|
@Override public int read() throws IOException {{ return -1; }}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn request_dispatcher(pkg: &str, http: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {pkg};
|
|
|
|
|
import {http}.HttpServletRequest;
|
|
|
|
|
import {http}.HttpServletResponse;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
public interface RequestDispatcher {{
|
|
|
|
|
void forward(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;
|
|
|
|
|
void include(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn web_servlet(pkg: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {pkg}.annotation;
|
|
|
|
|
import java.lang.annotation.ElementType;
|
|
|
|
|
import java.lang.annotation.Retention;
|
|
|
|
|
import java.lang.annotation.RetentionPolicy;
|
|
|
|
|
import java.lang.annotation.Target;
|
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
|
|
|
@Target(ElementType.TYPE)
|
|
|
|
|
public @interface WebServlet {{
|
|
|
|
|
String[] value() default {{}};
|
|
|
|
|
String[] urlPatterns() default {{}};
|
|
|
|
|
String name() default "";
|
|
|
|
|
int loadOnStartup() default -1;
|
|
|
|
|
boolean asyncSupported() default false;
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn http_servlet(pkg: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {pkg}.http;
|
|
|
|
|
import {pkg}.ServletException;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
public abstract class HttpServlet {{
|
|
|
|
|
public HttpServlet() {{}}
|
|
|
|
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {{}}
|
|
|
|
|
public String getServletInfo() {{ return ""; }}
|
|
|
|
|
public void init() throws ServletException {{}}
|
|
|
|
|
public void destroy() {{}}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn http_servlet_request(pkg: &str, http: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {http};
|
|
|
|
|
import {pkg}.RequestDispatcher;
|
|
|
|
|
import {pkg}.ServletInputStream;
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.StringReader;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.Enumeration;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
public class HttpServletRequest {{
|
|
|
|
|
private final Map<String, String> params = new HashMap<>();
|
|
|
|
|
private String method = "GET";
|
|
|
|
|
private String body = "";
|
|
|
|
|
public HttpServletRequest() {{}}
|
|
|
|
|
public void setParameter(String name, String value) {{ params.put(name, value); }}
|
|
|
|
|
public void setMethod(String m) {{ this.method = m; }}
|
|
|
|
|
public void setBody(String b) {{ this.body = b == null ? "" : b; }}
|
|
|
|
|
public String getBody() {{ return body; }}
|
|
|
|
|
public String getParameter(String name) {{ return params.get(name); }}
|
|
|
|
|
public String[] getParameterValues(String name) {{
|
|
|
|
|
String v = params.get(name);
|
|
|
|
|
return v == null ? null : new String[] {{ v }};
|
|
|
|
|
}}
|
|
|
|
|
public Map<String, String[]> getParameterMap() {{
|
|
|
|
|
Map<String, String[]> m = new HashMap<>();
|
|
|
|
|
for (Map.Entry<String, String> e : params.entrySet()) {{
|
|
|
|
|
m.put(e.getKey(), new String[] {{ e.getValue() }});
|
|
|
|
|
}}
|
|
|
|
|
return m;
|
|
|
|
|
}}
|
|
|
|
|
public Enumeration<String> getParameterNames() {{ return Collections.enumeration(params.keySet()); }}
|
|
|
|
|
public String getHeader(String name) {{ return null; }}
|
|
|
|
|
public Enumeration<String> getHeaders(String name) {{ return Collections.emptyEnumeration(); }}
|
|
|
|
|
public Enumeration<String> getHeaderNames() {{ return Collections.emptyEnumeration(); }}
|
|
|
|
|
public int getIntHeader(String name) {{ return -1; }}
|
|
|
|
|
public long getDateHeader(String name) {{ return -1L; }}
|
|
|
|
|
public Cookie[] getCookies() {{ return null; }}
|
|
|
|
|
public HttpSession getSession() {{ return new HttpSession(); }}
|
|
|
|
|
public HttpSession getSession(boolean create) {{ return new HttpSession(); }}
|
|
|
|
|
public ServletInputStream getInputStream() throws IOException {{ return null; }}
|
|
|
|
|
public BufferedReader getReader() throws IOException {{ return new BufferedReader(new StringReader(body)); }}
|
|
|
|
|
public String getMethod() {{ return method; }}
|
|
|
|
|
public String getQueryString() {{ return null; }}
|
|
|
|
|
public StringBuffer getRequestURL() {{ return new StringBuffer(); }}
|
|
|
|
|
public String getRequestURI() {{ return ""; }}
|
|
|
|
|
public String getRemoteAddr() {{ return "127.0.0.1"; }}
|
|
|
|
|
public String getRemoteHost() {{ return "localhost"; }}
|
|
|
|
|
public String getServletPath() {{ return ""; }}
|
|
|
|
|
public String getContextPath() {{ return ""; }}
|
|
|
|
|
public String getPathInfo() {{ return null; }}
|
|
|
|
|
public String getPathTranslated() {{ return null; }}
|
|
|
|
|
public Object getAttribute(String name) {{ return null; }}
|
|
|
|
|
public void setAttribute(String name, Object value) {{}}
|
|
|
|
|
public void removeAttribute(String name) {{}}
|
|
|
|
|
public Enumeration<String> getAttributeNames() {{ return Collections.emptyEnumeration(); }}
|
|
|
|
|
public String getCharacterEncoding() {{ return "UTF-8"; }}
|
|
|
|
|
public void setCharacterEncoding(String enc) {{}}
|
|
|
|
|
public String getContentType() {{ return null; }}
|
|
|
|
|
public int getContentLength() {{ return body == null ? 0 : body.length(); }}
|
|
|
|
|
public long getContentLengthLong() {{ return getContentLength(); }}
|
|
|
|
|
public String getProtocol() {{ return "HTTP/1.1"; }}
|
|
|
|
|
public String getScheme() {{ return "http"; }}
|
|
|
|
|
public String getServerName() {{ return "localhost"; }}
|
|
|
|
|
public int getServerPort() {{ return 80; }}
|
|
|
|
|
public Locale getLocale() {{ return Locale.getDefault(); }}
|
|
|
|
|
public Enumeration<Locale> getLocales() {{ return Collections.enumeration(Collections.singletonList(Locale.getDefault())); }}
|
|
|
|
|
public RequestDispatcher getRequestDispatcher(String path) {{ return null; }}
|
|
|
|
|
public boolean isSecure() {{ return false; }}
|
|
|
|
|
public String getAuthType() {{ return null; }}
|
|
|
|
|
public String getRemoteUser() {{ return null; }}
|
|
|
|
|
public java.security.Principal getUserPrincipal() {{ return null; }}
|
|
|
|
|
public boolean isUserInRole(String role) {{ return false; }}
|
|
|
|
|
public String getRequestedSessionId() {{ return null; }}
|
|
|
|
|
public boolean isRequestedSessionIdValid() {{ return false; }}
|
|
|
|
|
public boolean isRequestedSessionIdFromCookie() {{ return false; }}
|
|
|
|
|
public boolean isRequestedSessionIdFromURL() {{ return false; }}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn http_servlet_response(http: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {http};
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.OutputStream;
|
|
|
|
|
import java.io.PrintWriter;
|
|
|
|
|
import java.io.StringWriter;
|
2026-05-21 18:37:23 -05:00
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
2026-05-17 11:34:08 -05:00
|
|
|
public class HttpServletResponse {{
|
|
|
|
|
public static final int SC_OK = 200;
|
|
|
|
|
public static final int SC_NOT_FOUND = 404;
|
|
|
|
|
public static final int SC_FORBIDDEN = 403;
|
|
|
|
|
public static final int SC_UNAUTHORIZED = 401;
|
|
|
|
|
public static final int SC_INTERNAL_SERVER_ERROR = 500;
|
|
|
|
|
public static final int SC_MOVED_PERMANENTLY = 301;
|
|
|
|
|
public static final int SC_MOVED_TEMPORARILY = 302;
|
2026-05-21 18:37:23 -05:00
|
|
|
// Phase 08 (Track J.6): permissive header-capture buffer the Nyx
|
|
|
|
|
// harness drains after invoking the fixture's sink call. The buffer
|
|
|
|
|
// records every `setHeader` / `addHeader` write verbatim (including
|
|
|
|
|
// CRLF metacharacters); it does NOT mimic Tomcat 9+'s CRLF rejection
|
|
|
|
|
// so the differential rule can compare a real-fixture sanitiser path
|
|
|
|
|
// against an unsanitised one.
|
|
|
|
|
private static final List<String[]> nyxCapturedHeaders = new ArrayList<>();
|
2026-05-17 11:34:08 -05:00
|
|
|
private final StringWriter sw = new StringWriter();
|
|
|
|
|
private final PrintWriter pw = new PrintWriter(sw);
|
|
|
|
|
private int status = SC_OK;
|
2026-05-21 18:37:23 -05:00
|
|
|
private String redirectLocation = null;
|
2026-05-17 11:34:08 -05:00
|
|
|
public HttpServletResponse() {{}}
|
|
|
|
|
public PrintWriter getWriter() throws IOException {{ return pw; }}
|
|
|
|
|
public String getBody() {{ pw.flush(); return sw.toString(); }}
|
|
|
|
|
public OutputStream getOutputStream() throws IOException {{ return new java.io.ByteArrayOutputStream(); }}
|
|
|
|
|
public void setContentType(String type) {{}}
|
|
|
|
|
public String getContentType() {{ return null; }}
|
|
|
|
|
public void setCharacterEncoding(String enc) {{}}
|
|
|
|
|
public String getCharacterEncoding() {{ return "UTF-8"; }}
|
|
|
|
|
public void setContentLength(int len) {{}}
|
|
|
|
|
public void setContentLengthLong(long len) {{}}
|
|
|
|
|
public void setStatus(int sc) {{ this.status = sc; }}
|
|
|
|
|
public int getStatus() {{ return status; }}
|
|
|
|
|
public void sendError(int sc) throws IOException {{ this.status = sc; }}
|
|
|
|
|
public void sendError(int sc, String msg) throws IOException {{ this.status = sc; }}
|
2026-05-21 18:37:23 -05:00
|
|
|
public void sendRedirect(String location) throws IOException {{
|
|
|
|
|
this.status = SC_MOVED_TEMPORARILY;
|
|
|
|
|
this.redirectLocation = location;
|
|
|
|
|
}}
|
|
|
|
|
public String getRedirectedUrl() {{ return redirectLocation; }}
|
2026-05-17 11:34:08 -05:00
|
|
|
public void addCookie(Cookie cookie) {{}}
|
2026-05-21 18:37:23 -05:00
|
|
|
public void setHeader(String name, String value) {{
|
|
|
|
|
synchronized (nyxCapturedHeaders) {{
|
|
|
|
|
nyxCapturedHeaders.add(new String[]{{ name, value }});
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
public void addHeader(String name, String value) {{
|
|
|
|
|
synchronized (nyxCapturedHeaders) {{
|
|
|
|
|
nyxCapturedHeaders.add(new String[]{{ name, value }});
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
/** Drain every header recorded by {{@link #setHeader}} / {{@link #addHeader}} since
|
|
|
|
|
* the last drain. Used by the Nyx HEADER_INJECTION harness; returns
|
|
|
|
|
* pairs in insertion order. */
|
|
|
|
|
public static List<String[]> nyxDrainHeaders() {{
|
|
|
|
|
synchronized (nyxCapturedHeaders) {{
|
|
|
|
|
List<String[]> snap = new ArrayList<>(nyxCapturedHeaders);
|
|
|
|
|
nyxCapturedHeaders.clear();
|
|
|
|
|
return snap;
|
|
|
|
|
}}
|
|
|
|
|
}}
|
2026-05-17 11:34:08 -05:00
|
|
|
public void setIntHeader(String name, int value) {{}}
|
|
|
|
|
public void addIntHeader(String name, int value) {{}}
|
|
|
|
|
public void setDateHeader(String name, long date) {{}}
|
|
|
|
|
public void addDateHeader(String name, long date) {{}}
|
|
|
|
|
public boolean containsHeader(String name) {{ return false; }}
|
|
|
|
|
public String getHeader(String name) {{ return null; }}
|
|
|
|
|
public java.util.Collection<String> getHeaders(String name) {{ return java.util.Collections.emptyList(); }}
|
|
|
|
|
public java.util.Collection<String> getHeaderNames() {{ return java.util.Collections.emptyList(); }}
|
|
|
|
|
public String encodeURL(String url) {{ return url; }}
|
|
|
|
|
public String encodeRedirectURL(String url) {{ return url; }}
|
|
|
|
|
public void flushBuffer() throws IOException {{}}
|
|
|
|
|
public void resetBuffer() {{}}
|
|
|
|
|
public void reset() {{}}
|
|
|
|
|
public boolean isCommitted() {{ return false; }}
|
|
|
|
|
public void setBufferSize(int size) {{}}
|
|
|
|
|
public int getBufferSize() {{ return 0; }}
|
|
|
|
|
public void setLocale(java.util.Locale loc) {{}}
|
|
|
|
|
public java.util.Locale getLocale() {{ return java.util.Locale.getDefault(); }}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn http_session(http: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {http};
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.Enumeration;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
public class HttpSession {{
|
|
|
|
|
private final Map<String, Object> attrs = new HashMap<>();
|
|
|
|
|
public HttpSession() {{}}
|
|
|
|
|
public String getId() {{ return "stub-session"; }}
|
|
|
|
|
public void setAttribute(String name, Object value) {{ attrs.put(name, value); }}
|
|
|
|
|
public Object getAttribute(String name) {{ return attrs.get(name); }}
|
|
|
|
|
public void removeAttribute(String name) {{ attrs.remove(name); }}
|
|
|
|
|
public Enumeration<String> getAttributeNames() {{ return Collections.enumeration(attrs.keySet()); }}
|
|
|
|
|
public long getCreationTime() {{ return 0L; }}
|
|
|
|
|
public long getLastAccessedTime() {{ return 0L; }}
|
|
|
|
|
public int getMaxInactiveInterval() {{ return 0; }}
|
|
|
|
|
public void setMaxInactiveInterval(int interval) {{}}
|
|
|
|
|
public boolean isNew() {{ return true; }}
|
|
|
|
|
public void invalidate() {{}}
|
|
|
|
|
public void putValue(String name, Object value) {{ attrs.put(name, value); }}
|
|
|
|
|
public Object getValue(String name) {{ return attrs.get(name); }}
|
|
|
|
|
public String[] getValueNames() {{ return attrs.keySet().toArray(new String[0]); }}
|
|
|
|
|
public void removeValue(String name) {{ attrs.remove(name); }}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn cookie(http: &str) -> String {
|
|
|
|
|
format!(
|
|
|
|
|
r#"package {http};
|
|
|
|
|
public class Cookie implements Cloneable {{
|
|
|
|
|
private String name;
|
|
|
|
|
private String value;
|
|
|
|
|
private String path;
|
|
|
|
|
private String domain;
|
|
|
|
|
private String comment;
|
|
|
|
|
private int maxAge = -1;
|
|
|
|
|
private int version = 0;
|
|
|
|
|
private boolean secure;
|
|
|
|
|
private boolean httpOnly;
|
|
|
|
|
public Cookie() {{}}
|
|
|
|
|
public Cookie(String name, String value) {{ this.name = name; this.value = value; }}
|
|
|
|
|
public String getName() {{ return name; }}
|
|
|
|
|
public String getValue() {{ return value; }}
|
|
|
|
|
public void setValue(String value) {{ this.value = value; }}
|
|
|
|
|
public void setPath(String path) {{ this.path = path; }}
|
|
|
|
|
public String getPath() {{ return path; }}
|
|
|
|
|
public void setDomain(String domain) {{ this.domain = domain; }}
|
|
|
|
|
public String getDomain() {{ return domain; }}
|
|
|
|
|
public void setMaxAge(int age) {{ this.maxAge = age; }}
|
|
|
|
|
public int getMaxAge() {{ return maxAge; }}
|
|
|
|
|
public void setSecure(boolean secure) {{ this.secure = secure; }}
|
|
|
|
|
public boolean getSecure() {{ return secure; }}
|
|
|
|
|
public void setHttpOnly(boolean httpOnly) {{ this.httpOnly = httpOnly; }}
|
|
|
|
|
public boolean isHttpOnly() {{ return httpOnly; }}
|
|
|
|
|
public void setVersion(int v) {{ this.version = v; }}
|
|
|
|
|
public int getVersion() {{ return version; }}
|
|
|
|
|
public void setComment(String c) {{ this.comment = c; }}
|
|
|
|
|
public String getComment() {{ return comment; }}
|
|
|
|
|
@Override public Object clone() {{ try {{ return super.clone(); }} catch (CloneNotSupportedException e) {{ throw new RuntimeException(e); }} }}
|
|
|
|
|
}}
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn ships_both_javax_and_jakarta_trees() {
|
|
|
|
|
let files = servlet_stub_files();
|
|
|
|
|
let paths: Vec<&str> = files.iter().map(|(p, _)| p.as_str()).collect();
|
|
|
|
|
assert!(paths.contains(&"javax/servlet/http/HttpServletRequest.java"));
|
|
|
|
|
assert!(paths.contains(&"javax/servlet/ServletException.java"));
|
|
|
|
|
assert!(paths.contains(&"javax/servlet/annotation/WebServlet.java"));
|
|
|
|
|
assert!(paths.contains(&"jakarta/servlet/http/HttpServletRequest.java"));
|
|
|
|
|
assert!(paths.contains(&"jakarta/servlet/ServletException.java"));
|
|
|
|
|
assert!(paths.contains(&"jakarta/servlet/annotation/WebServlet.java"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn bundle_has_eighteen_files() {
|
|
|
|
|
// Nine stubs per package tree, two trees. A drift here usually
|
|
|
|
|
// means a stub was added without updating the count assertion or
|
|
|
|
|
// a stub got accidentally dropped.
|
|
|
|
|
let files = servlet_stub_files();
|
|
|
|
|
assert_eq!(files.len(), 18, "expected 9 javax + 9 jakarta stubs");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn http_servlet_request_carries_owasp_method_surface() {
|
|
|
|
|
// OWASP Benchmark v1.2 fixtures exercise this surface; if any of
|
|
|
|
|
// these getters disappears the bundle will start producing
|
|
|
|
|
// BuildFailed verdicts on the corresponding fixtures.
|
|
|
|
|
let req = http_servlet_request("javax.servlet", "javax.servlet.http");
|
|
|
|
|
for method in &[
|
|
|
|
|
"getCookies",
|
|
|
|
|
"getHeader",
|
|
|
|
|
"getHeaderNames",
|
|
|
|
|
"getHeaders",
|
|
|
|
|
"getInputStream",
|
|
|
|
|
"getParameter",
|
|
|
|
|
"getParameterMap",
|
|
|
|
|
"getParameterNames",
|
|
|
|
|
"getParameterValues",
|
|
|
|
|
"getQueryString",
|
|
|
|
|
"getSession",
|
|
|
|
|
"getReader",
|
|
|
|
|
] {
|
|
|
|
|
assert!(
|
|
|
|
|
req.contains(method),
|
|
|
|
|
"javax HttpServletRequest stub missing `{method}`"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn http_servlet_response_carries_owasp_method_surface() {
|
|
|
|
|
let resp = http_servlet_response("javax.servlet.http");
|
|
|
|
|
for method in &["addCookie", "getWriter", "setContentType"] {
|
|
|
|
|
assert!(
|
|
|
|
|
resp.contains(method),
|
|
|
|
|
"javax HttpServletResponse stub missing `{method}`"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 18:37:23 -05:00
|
|
|
#[test]
|
|
|
|
|
fn http_servlet_response_captures_headers_for_phase_08() {
|
|
|
|
|
// Phase 08 (Track J.6): the response stub records every
|
|
|
|
|
// setHeader / addHeader write into a static buffer the
|
|
|
|
|
// HEADER_INJECTION harness drains after invoking the fixture's
|
|
|
|
|
// sink call. Tomcat-style CRLF rejection is intentionally NOT
|
|
|
|
|
// modeled so the differential rule can compare a real-fixture
|
|
|
|
|
// sanitiser path against an unsanitised one.
|
|
|
|
|
for pkg in &["javax.servlet.http", "jakarta.servlet.http"] {
|
|
|
|
|
let resp = http_servlet_response(pkg);
|
|
|
|
|
for marker in &[
|
|
|
|
|
"nyxCapturedHeaders",
|
|
|
|
|
"nyxDrainHeaders",
|
|
|
|
|
"synchronized (nyxCapturedHeaders)",
|
|
|
|
|
] {
|
|
|
|
|
assert!(
|
|
|
|
|
resp.contains(marker),
|
|
|
|
|
"{pkg} HttpServletResponse stub missing `{marker}`",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn http_servlet_response_keeps_redirect_capture_for_phase_09() {
|
|
|
|
|
// Phase 09 (Track J.7): the response stub records the most
|
|
|
|
|
// recent sendRedirect target so the OPEN_REDIRECT harness can
|
|
|
|
|
// mirror the Phase 08 header-capture pattern when the bootstrap
|
|
|
|
|
// path lands. Pin the capture wiring up-front so a future
|
|
|
|
|
// refactor cannot silently drop it.
|
|
|
|
|
for pkg in &["javax.servlet.http", "jakarta.servlet.http"] {
|
|
|
|
|
let resp = http_servlet_response(pkg);
|
|
|
|
|
assert!(
|
2026-05-22 09:42:18 -05:00
|
|
|
resp.contains("redirectLocation") && resp.contains("getRedirectedUrl"),
|
2026-05-21 18:37:23 -05:00
|
|
|
"{pkg} HttpServletResponse stub missing redirect-capture wiring",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 11:34:08 -05:00
|
|
|
#[test]
|
|
|
|
|
fn http_servlet_request_keeps_reflective_hook_setters() {
|
|
|
|
|
// The Java emitter's SERVLET_HELPER uses reflection to invoke
|
|
|
|
|
// setParameter / setMethod / setBody on the stub request before
|
|
|
|
|
// the entry method runs. Dropping any of these would silently
|
|
|
|
|
// break payload seeding for OWASP-shape harnesses.
|
|
|
|
|
for pkg in &["javax.servlet", "jakarta.servlet"] {
|
|
|
|
|
let http = format!("{pkg}.http");
|
|
|
|
|
let req = http_servlet_request(pkg, &http);
|
|
|
|
|
for method in &["setParameter", "setMethod", "setBody"] {
|
|
|
|
|
assert!(
|
|
|
|
|
req.contains(method),
|
|
|
|
|
"{pkg} HttpServletRequest stub missing `{method}`"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn jakarta_stubs_carry_jakarta_package_declaration() {
|
|
|
|
|
let files = servlet_stub_files();
|
|
|
|
|
for (path, body) in &files {
|
|
|
|
|
if path.starts_with("jakarta/") {
|
|
|
|
|
assert!(
|
|
|
|
|
body.contains("package jakarta.servlet"),
|
|
|
|
|
"jakarta stub at {path} missing jakarta package declaration"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
!body.contains("package javax.servlet"),
|
|
|
|
|
"jakarta stub at {path} accidentally carries javax package"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|