I want to implement passes bundle support in my web application. Generating single .pkpass files works great. Files generated by my Java servlet open fine in both Chrome and Safari.
I am now creating passes bundles as the documentation says.
The servlet requested from Chrome opens the bundle and displays it in Apple Wallet correctly.
However, it does not work in Safari – both, desktop and iPhone. The iOS version is 17.5.1. Safari either returns no message, but displays the error “Safari cannot download this file”.
The servlet looks like this:
<code>@GetMapping(value = "ticket")
public Object getTicketForOrder(HttpServletRequest request, HttpServletResponse response, Integer ticketId) {
.. generate .pkpass files
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(bos);
for (PassFile file : files ) {
ZipEntry zipEntry = new ZipEntry(file.getFileName());
zipOut.putNextEntry(zipEntry);
zipOut.write(file.getContent());
response.setContentType("application/vnd.apple.pkpasses");
response.setHeader("Content-Disposition", "attachment; filename=tickets.pkpasses");
response.setContentLength(bos.size());
OutputStream os = response.getOutputStream();
<code>@GetMapping(value = "ticket")
public Object getTicketForOrder(HttpServletRequest request, HttpServletResponse response, Integer ticketId) {
...
.. generate .pkpass files
...
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(bos);
for (PassFile file : files ) {
ZipEntry zipEntry = new ZipEntry(file.getFileName());
zipOut.putNextEntry(zipEntry);
zipOut.write(file.getContent());
zipOut.closeEntry();
}
zipOut.finish();
zipOut.flush();
zipOut.close();
response.setStatus(200);
response.setContentType("application/vnd.apple.pkpasses");
response.setHeader("Content-Disposition", "attachment; filename=tickets.pkpasses");
response.setContentLength(bos.size());
OutputStream os = response.getOutputStream();
bos.writeTo(os);
os.flush();
os.close();
bos.close();
return null;
}
</code>
@GetMapping(value = "ticket")
public Object getTicketForOrder(HttpServletRequest request, HttpServletResponse response, Integer ticketId) {
...
.. generate .pkpass files
...
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(bos);
for (PassFile file : files ) {
ZipEntry zipEntry = new ZipEntry(file.getFileName());
zipOut.putNextEntry(zipEntry);
zipOut.write(file.getContent());
zipOut.closeEntry();
}
zipOut.finish();
zipOut.flush();
zipOut.close();
response.setStatus(200);
response.setContentType("application/vnd.apple.pkpasses");
response.setHeader("Content-Disposition", "attachment; filename=tickets.pkpasses");
response.setContentLength(bos.size());
OutputStream os = response.getOutputStream();
bos.writeTo(os);
os.flush();
os.close();
bos.close();
return null;
}
This is what Curl returns:
<code>curl -v example.com/ticket... --output tickets.pkpasses
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 178.25.168.59:443...
* Connected to .... port 443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* start date: May 6 00:00:00 2024 GMT
* expire date: May 5 23:59:59 2025 GMT
* issuer: C=US; O=Vitalwerks Internet Solutions, LLC; CN=Vitalwerks Internet Solutions, No-IP TLS ICA
* SSL certificate verify ok.
> User-Agent: curl/7.88.1
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
0 0 0 0 0 0 0 0 --:--:-- 0:00:03 --:--:-- 0{ [5 bytes data]
0 0 0 0 0 0 0 0 --:--:-- 0:00:04 --:--:-- 0< HTTP/1.1 200
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Set-Cookie: JSESSIONID=...........; HttpOnly
< Content-Disposition: attachment; filename=tickets.pkpasses
< X-Content-Type-Options: nosniff
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Strict-Transport-Security: max-age=31536000 ; includeSubDomains
< Content-Type: application/vnd.apple.pkpasses
< Content-Length: 3357314
< Date: Sat, 22 Jun 2024 13:07:55 GMT
100 3278k 100 3278k 0 0 753k 0 0:00:04 0:00:04 --:--:-- 753k
* Connection #0 to host..... left intact
<code>curl -v example.com/ticket... --output tickets.pkpasses
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 178.25.168.59:443...
* Connected to .... port 443 (#0)
* ALPN: offers h2,http/1.1
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* CAfile: /....crt
* CApath: /....
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [10 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [4254 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=.......
* start date: May 6 00:00:00 2024 GMT
* expire date: May 5 23:59:59 2025 GMT
* issuer: C=US; O=Vitalwerks Internet Solutions, LLC; CN=Vitalwerks Internet Solutions, No-IP TLS ICA
* SSL certificate verify ok.
* using HTTP/1.x
} [5 bytes data]
> GET .... HTTP/1.1
> Host: .....
> User-Agent: curl/7.88.1
> Accept: */*
>
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [265 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [265 bytes data]
* old SSL session ID is stale, removing
0 0 0 0 0 0 0 0 --:--:-- 0:00:03 --:--:-- 0{ [5 bytes data]
0 0 0 0 0 0 0 0 --:--:-- 0:00:04 --:--:-- 0< HTTP/1.1 200
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Set-Cookie: JSESSIONID=...........; HttpOnly
< Content-Disposition: attachment; filename=tickets.pkpasses
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 0
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< Strict-Transport-Security: max-age=31536000 ; includeSubDomains
< X-Frame-Options: DENY
< Content-Type: application/vnd.apple.pkpasses
< Content-Length: 3357314
< Date: Sat, 22 Jun 2024 13:07:55 GMT
<
{ [15644 bytes data]
100 3278k 100 3278k 0 0 753k 0 0:00:04 0:00:04 --:--:-- 753k
* Connection #0 to host..... left intact
</code>
curl -v example.com/ticket... --output tickets.pkpasses
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 178.25.168.59:443...
* Connected to .... port 443 (#0)
* ALPN: offers h2,http/1.1
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* CAfile: /....crt
* CApath: /....
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [10 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [4254 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=.......
* start date: May 6 00:00:00 2024 GMT
* expire date: May 5 23:59:59 2025 GMT
* issuer: C=US; O=Vitalwerks Internet Solutions, LLC; CN=Vitalwerks Internet Solutions, No-IP TLS ICA
* SSL certificate verify ok.
* using HTTP/1.x
} [5 bytes data]
> GET .... HTTP/1.1
> Host: .....
> User-Agent: curl/7.88.1
> Accept: */*
>
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [265 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [265 bytes data]
* old SSL session ID is stale, removing
0 0 0 0 0 0 0 0 --:--:-- 0:00:03 --:--:-- 0{ [5 bytes data]
0 0 0 0 0 0 0 0 --:--:-- 0:00:04 --:--:-- 0< HTTP/1.1 200
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Set-Cookie: JSESSIONID=...........; HttpOnly
< Content-Disposition: attachment; filename=tickets.pkpasses
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 0
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< Strict-Transport-Security: max-age=31536000 ; includeSubDomains
< X-Frame-Options: DENY
< Content-Type: application/vnd.apple.pkpasses
< Content-Length: 3357314
< Date: Sat, 22 Jun 2024 13:07:55 GMT
<
{ [15644 bytes data]
100 3278k 100 3278k 0 0 753k 0 0:00:04 0:00:04 --:--:-- 753k
* Connection #0 to host..... left intact
As for me, everything is as it should be.
Why does it work in Chrome and not work in Safari?
Do you have any idea how to debug the issue?
Have you had similar problems with .pkpasses bundles on Safari?