Return 403 when access R2 via HttpURLConnection in Java

For Workers & Pages, what is the name of the domain?

chinese-workday-calendar.aops.io

What is the error number?

403

What is the error message?

403

What is the issue or error you’re encountering

Get 403 when access to public R2 bucket via custom domain url via HttpURLConnection in Java

What are the steps to reproduce the issue?

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

class Scratch {

    public static void main(String[] args) throws IOException {
        URL url = new URL(args[0]);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//        connection.setRequestProperty("User-Agent", "Mozilla/5.0");
        connection.setConnectTimeout(3000);
        connection.setReadTimeout(10000);
        connection.connect();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
$ java -version
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

$ javac scratch.java
$ java Scratch https://chinese-workday-calendar.aops.io/years.properties
Exception in thread "main" java.io.IOException: Server returned HTTP response code: 403 for URL: https://chinese-workday-calendar.aops.io/years.properties

You could also reproduce via curl add a explicit User-Agent header:

$ curl -i https://chinese-workday-calendar.aops.io/years.properties -H 'User-Agent: Java/1.8.0_201'
HTTP/2 403
date: Thu, 13 Feb 2025 01:49:32 GMT
content-type: text/plain; charset=UTF-8
content-length: 16
x-frame-options: SAMEORIGIN
referrer-policy: same-origin
cache-control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
expires: Thu, 01 Jan 1970 00:00:01 GMT
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=MTWV3ZSVlJtqeKYiv3dxxz0LSevet0RcpauwmLuj1CAPWstZTxKqH9CiIaGGQtnTgqV2StklgSU9pkpdLisFc0nb6Ddzu%2BlrvzorvyAJrY2200ZnUCMOXgSM8Hxpl%2FsZrcV8v8F26ZxOd1fVqhQHXm66ng%3D%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 91112993e9060ed8-HKG
alt-svc: h3=":443"; ma=86400
server-timing: cfL4;desc="?proto=TCP&rtt=38371&min_rtt=38152&rtt_var=11163&sent=6&recv=10&lost=0&retrans=0&sent_bytes=3467&recv_bytes=816&delivery_rate=72719&cwnd=234&unsent_bytes=0&cid=31599be2002c75c7&ts=53&x=0"

error code: 1010

Workaround:
Explicit set a user-agent header via java.net.URLConnection#setRequestProperty like this: connection.setRequestProperty("User-Agent", "Mozilla/5.0");

Looks like the server just met recognization issue for user-agent header with this pattern: Java/{number}.{number}.{number}

I can not reproduce this behavior on any of my R2 buckets using Custom Domains. Here’s one example:

curl -svo out.jpg https://r2.b8a.me/geio-tischler-VKD4GpTlscM-unsplash.jpg -H 'User-Agent: Java/1.8.0_201'

And it dumps the image file with a 200 response.

Could you able to reproduce it with this command:

curl -i https://chinese-workday-calendar.aops.io/years.properties -H ‘User-Agent: Java/1.8.0_201’

Probably it is region specific issue, I just success reproduce this problem with your command, here is full command output:

curl -svo out.jpg https://r2.b8a.me/geio-tischler-VKD4GpTlscM-unsplash.jpg -H 'User-Agent: Java/1.8.0_201'
*   Trying 104.26.8.134:443...
* Connected to r2.b8a.me (104.26.8.134) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /Users/****/anaconda3/ssl/cacert.pem
*  CApath: none
} [5 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [19 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [2514 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [80 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=b8a.me
*  start date: Jan 16 17:19:14 2025 GMT
*  expire date: Apr 16 18:18:56 2025 GMT
*  subjectAltName: host "r2.b8a.me" matched cert's "*.b8a.me"
*  issuer: C=US; O=Google Trust Services; CN=WE1
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
} [5 bytes data]
* h2h3 [:method: GET]
* h2h3 [:path: /geio-tischler-VKD4GpTlscM-unsplash.jpg]
* h2h3 [:scheme: https]
* h2h3 [:authority: r2.b8a.me]
* h2h3 [accept: */*]
* h2h3 [user-agent: Java/1.8.0_201]
* Using Stream ID: 1 (easy handle 0x13a80e000)
} [5 bytes data]
> GET /geio-tischler-VKD4GpTlscM-unsplash.jpg HTTP/2
> Host: r2.b8a.me
> accept: */*
> user-agent: Java/1.8.0_201
>
{ [5 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [238 bytes data]
* [CONN-0-0][CF-SSL] TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [238 bytes data]
* old SSL session ID is stale, removing
{ [5 bytes data]
< HTTP/2 403
< date: Thu, 13 Feb 2025 05:16:49 GMT
< content-type: text/html; charset=UTF-8
< accept-ch: Sec-CH-UA-Bitness, Sec-CH-UA-Arch, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform, Sec-CH-UA, UA-Bitness, UA-Arch, UA-Full-Version, UA-Mobile, UA-Model, UA-Platform-Version, UA-Platform, UA
< critical-ch: Sec-CH-UA-Bitness, Sec-CH-UA-Arch, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform, Sec-CH-UA, UA-Bitness, UA-Arch, UA-Full-Version, UA-Mobile, UA-Model, UA-Platform-Version, UA-Platform, UA
< cross-origin-embedder-policy: require-corp
< cross-origin-opener-policy: same-origin
< cross-origin-resource-policy: same-origin
< origin-agent-cluster: ?1
< permissions-policy: accelerometer=(),autoplay=(),browsing-topics=(),camera=(),clipboard-read=(),clipboard-write=(),geolocation=(),gyroscope=(),hid=(),interest-cohort=(),magnetometer=(),microphone=(),payment=(),publickey-credentials-get=(),screen-wake-lock=(),serial=(),sync-xhr=(),usb=()
< referrer-policy: same-origin
< x-content-options: nosniff
< x-frame-options: SAMEORIGIN
< cf-mitigated: challenge
< server-timing: chlray;desc="9112593a7f5102da"
< cf-chl-out: do1TPtMrkms/Mtxj5/IQYkLDF5cxvHIdMuhZA3P+GNo4yg7KdUxZfd4clfjNrtqzkfquAynsRTS/XEM5iaJP9uhpwqk4ZjQDvKeipC9VJ+WmWi3K9htT1otJmij4fz4rPE22wSALC9uu87tALNLJtw==$EHoj333qW/Kb3XJ4EhcYGw==
< cache-control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< expires: Thu, 01 Jan 1970 00:00:01 GMT
< report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=97QUJyq5WjX34z2wlDAh4NOpfjyCwyD%2FDcvgSMmNyqrJHStMYS7lBilbp6bkSos97OBdUkiR0tyEnbxoeFSIQxVYBBeEb5tfyDtLaUTY6m3xEJqrhZYt1ZXUVQ%3D%3D"}],"group":"cf-nel","max_age":604800}
< nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
< strict-transport-security: max-age=31536000; includeSubDomains; preload
< x-content-type-options: nosniff
< server: cloudflare
< cf-ray: 9112593a7f5102da-HKG
< alt-svc: h3=":443"; ma=86400
< server-timing: cfL4;desc="?proto=TCP&rtt=38998&min_rtt=38823&rtt_var=14909&sent=7&recv=9&lost=0&retrans=0&sent_bytes=3411&recv_bytes=817&delivery_rate=71991&cwnd=133&unsent_bytes=0&cid=fd3c94f9f1ad0084&ts=54&x=0"
<
{ [5 bytes data]
* Connection #0 to host r2.b8a.me left intact

My location is near at PEK airport.

It looks like it’s triggering Browser Integrity Check. Though I don’t know why it’s regional. You should be able to see it in your Security Events Log.

1 Like

Thanks. I’m new to Cloudflare, how to look at the Security Events Log btw?

I checked Security Center > Security Insights, it is empty there.

You’re looking under Account information. You need to go to the domain first, and look at the Security Section where your firewall rules are:

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.