@@ -3196,6 +3196,183 @@ public void xmlHttpRequestGet() throws Exception {
31963196 }
31973197 }
31983198
3199+ /**
3200+ * Tests the CORS preflight (OPTIONS) request triggered by a cross-origin XHR using a
3201+ * non-simple method (PUT) and a custom header. This is a structurally distinct request
3202+ * category from anything else in this file - no payload, Access-Control-Request-Method/
3203+ * Access-Control-Request-Headers instead of a body - and it's not yet known whether
3204+ * Sec-Fetch-* headers even apply to the preflight itself as opposed to only the actual
3205+ * follow-up request.
3206+ * <p>
3207+ * NOTE: needs two concurrent {@link PrimitiveWebServer} instances (one per origin), same
3208+ * as {@link #redirectToCrossSiteHost()} - unverified whether the constructor used here
3209+ * actually supports that; double check before relying on it.
3210+ *
3211+ * @throws Exception if the test fails
3212+ */
3213+ @ Test
3214+ @ Alerts (CHROME = {"OPTIONS /target HTTP/1.1" ,
3215+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3216+ "Connection: keep-alive" ,
3217+ "Accept: */*" ,
3218+ "Access-Control-Request-Method: PUT" ,
3219+ "Access-Control-Request-Headers: x-custom" ,
3220+ "Origin: http://localhost:22225" ,
3221+ "User-Agent: §§USER_AGENT§§" ,
3222+ "Sec-Fetch-Mode: cors" ,
3223+ "Referer: http://localhost:22225/" ,
3224+ "Accept-Encoding: gzip, deflate" ,
3225+ "Accept-Language: en-US,en;q=0.9" },
3226+ EDGE = {"OPTIONS /target HTTP/1.1" ,
3227+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3228+ "Connection: keep-alive" ,
3229+ "Accept: */*" ,
3230+ "Access-Control-Request-Method: PUT" ,
3231+ "Access-Control-Request-Headers: x-custom" ,
3232+ "Origin: http://localhost:22225" ,
3233+ "User-Agent: §§USER_AGENT§§" ,
3234+ "Sec-Fetch-Mode: cors" ,
3235+ "Referer: http://localhost:22225/" ,
3236+ "Accept-Encoding: gzip, deflate" ,
3237+ "Accept-Language: en-US,en;q=0.9" },
3238+ FF = {"OPTIONS /target HTTP/1.1" ,
3239+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3240+ "User-Agent: §§USER_AGENT§§" ,
3241+ "Accept: */*" ,
3242+ "Accept-Language: en-US,en;q=0.9" ,
3243+ "Accept-Encoding: gzip, deflate" ,
3244+ "Access-Control-Request-Method: PUT" ,
3245+ "Access-Control-Request-Headers: x-custom" ,
3246+ "Referer: http://localhost:22225/" ,
3247+ "Origin: http://localhost:22225" ,
3248+ "Connection: keep-alive" ,
3249+ "Priority: u=4" },
3250+ FF_ESR = {"OPTIONS /target HTTP/1.1" ,
3251+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3252+ "User-Agent: §§USER_AGENT§§" ,
3253+ "Accept: */*" ,
3254+ "Accept-Language: en-US,en;q=0.5" ,
3255+ "Accept-Encoding: gzip, deflate" ,
3256+ "Access-Control-Request-Method: PUT" ,
3257+ "Access-Control-Request-Headers: x-custom" ,
3258+ "Referer: http://localhost:22225/" ,
3259+ "Origin: http://localhost:22225" ,
3260+ "Connection: keep-alive" ,
3261+ "Priority: u=4" })
3262+ @ HtmlUnitNYI (
3263+ CHROME = {"OPTIONS /target HTTP/1.1" ,
3264+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3265+ "Connection: keep-alive" ,
3266+ "User-Agent: §§USER_AGENT§§" ,
3267+ "Accept: */*" ,
3268+ "Sec-Fetch-Mode: cors" ,
3269+ "Referer: http://localhost:22225/" ,
3270+ "Accept-Encoding: gzip, deflate" ,
3271+ "Accept-Language: en-US,en;q=0.9" ,
3272+ "Origin: http://localhost:22225" ,
3273+ "Access-Control-Request-Method: PUT" ,
3274+ "Access-Control-Request-Headers: x-custom" ,
3275+ "Content-Length: 0" }, // wrong
3276+ EDGE = {"OPTIONS /target HTTP/1.1" ,
3277+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3278+ "Connection: keep-alive" ,
3279+ "User-Agent: §§USER_AGENT§§" ,
3280+ "Accept: */*" ,
3281+ "Sec-Fetch-Mode: cors" ,
3282+ "Referer: http://localhost:22225/" ,
3283+ "Accept-Encoding: gzip, deflate" ,
3284+ "Accept-Language: en-US,en;q=0.9" ,
3285+ "Origin: http://localhost:22225" ,
3286+ "Access-Control-Request-Method: PUT" ,
3287+ "Access-Control-Request-Headers: x-custom" ,
3288+ "Content-Length: 0" }, // wrong
3289+ FF = {"OPTIONS /target HTTP/1.1" ,
3290+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3291+ "User-Agent: §§USER_AGENT§§" ,
3292+ "Accept: */*" ,
3293+ "Accept-Language: en-US,en;q=0.9" ,
3294+ "Accept-Encoding: gzip, deflate" ,
3295+ "Connection: keep-alive" ,
3296+ "Referer: http://localhost:22225/" ,
3297+ "Priority: u=0, i" ,
3298+ "Origin: http://localhost:22225" ,
3299+ "Access-Control-Request-Method: PUT" ,
3300+ "Access-Control-Request-Headers: x-custom" ,
3301+ "Content-Length: 0" }, // wrong
3302+ FF_ESR = {"OPTIONS /target HTTP/1.1" ,
3303+ "Host: host1.htmlunit-dev.org:§§PORT§§" ,
3304+ "User-Agent: §§USER_AGENT§§" ,
3305+ "Accept: */*" ,
3306+ "Accept-Language: en-US,en;q=0.5" ,
3307+ "Accept-Encoding: gzip, deflate" ,
3308+ "Connection: keep-alive" ,
3309+ "Referer: http://localhost:22225/" ,
3310+ "Priority: u=0, i" ,
3311+ "Origin: http://localhost:22225" ,
3312+ "Access-Control-Request-Method: PUT" ,
3313+ "Access-Control-Request-Headers: x-custom" ,
3314+ "Content-Length: 0" }) // wrong
3315+ public void xmlHttpRequestPreflight () throws Exception {
3316+ final String preflightResponse = "HTTP/1.1 200 OK\r \n "
3317+ + "Content-Length: 0\r \n "
3318+ + "Access-Control-Allow-Origin: *\r \n "
3319+ + "Access-Control-Allow-Methods: PUT\r \n "
3320+ + "Access-Control-Allow-Headers: X-Custom\r \n "
3321+ + "Connection: close\r \n "
3322+ + "\r \n " ;
3323+ final String actualResponse = "HTTP/1.1 200 OK\r \n "
3324+ + "Content-Length: 2\r \n "
3325+ + "Content-Type: text/plain\r \n "
3326+ + "Access-Control-Allow-Origin: *\r \n "
3327+ + "Connection: close\r \n "
3328+ + "\r \n "
3329+ + "Hi" ;
3330+
3331+ shutDownAll ();
3332+ try (PrimitiveWebServer crossServer = new PrimitiveWebServer (PORT_PROXY_SERVER ,
3333+ null , preflightResponse , actualResponse , actualResponse )) {
3334+ final String html = DOCTYPE_HTML
3335+ + "<html><head><script>\n "
3336+ + " function doXhr() {\n "
3337+ + " var x = new XMLHttpRequest();\n "
3338+ + " x.open('PUT', 'http://host1.htmlunit-dev.org:" + crossServer .getPort () + "/target', true);\n "
3339+ + " x.setRequestHeader('X-Custom', 'value');\n "
3340+ + " x.send('body');\n "
3341+ + " }\n "
3342+ + "</script></head>\n "
3343+ + "<body onload='doXhr()'></body></html>" ;
3344+ final String htmlResponse = "HTTP/1.1 200 OK\r \n "
3345+ + "Content-Length: " + html .length () + "\r \n "
3346+ + "Content-Type: text/html\r \n "
3347+ + "Connection: close\r \n "
3348+ + "\r \n "
3349+ + html ;
3350+
3351+ try (PrimitiveWebServer originServer = new PrimitiveWebServer (null , htmlResponse , htmlResponse )) {
3352+ final WebDriver driver = getWebDriver ();
3353+
3354+ driver .get ("http://localhost:" + originServer .getPort ());
3355+
3356+ final long endTime = System .currentTimeMillis () + Duration .ofSeconds (4 ).toMillis ();
3357+ while (crossServer .getRequests ().size () < 2
3358+ && System .currentTimeMillis () < endTime ) {
3359+ Thread .sleep (100 );
3360+ }
3361+
3362+ if (crossServer .getRequests ().size () < 2 ) {
3363+ Assertions .fail ("Still no request / request count:" + crossServer .getRequests ().size ());
3364+ }
3365+
3366+ final String [] expectedHeaders = getExpectedAlertsWithHtmlReplacement (crossServer );
3367+
3368+ // request 0 = the OPTIONS preflight
3369+ final String request = crossServer .getRequests ().get (0 );
3370+ final String [] headers = request .split ("\\ r\\ n" );
3371+ assertEquals (Arrays .asList (expectedHeaders ).toString (), Arrays .asList (headers ).toString ());
3372+ }
3373+ }
3374+ }
3375+
31993376 /**
32003377 * Tests a form submitted purely by script ({@code form.submit()}), as opposed to via
32013378 * a click on a submit control. Real browsers do not consider this a user-activated
0 commit comments