Skip to content

Commit 0f1f50b

Browse files
committed
Add android-fetchurl sample
1 parent 4aa2289 commit 0f1f50b

File tree

7 files changed

+243
-0
lines changed

7 files changed

+243
-0
lines changed

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: swift-android-samples ci
2+
on:
3+
push:
4+
branches: [ main ]
5+
workflow_dispatch:
6+
pull_request:
7+
branches:
8+
- '*'
9+
schedule:
10+
- cron: '45 2,13 * * *'
11+
jobs:
12+
run-samples:
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
swift: ['6.1', 'nightly-6.2']
17+
runs-on: macos-13
18+
timeout-minutes: 60
19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: "Build Swift Package for Android (Skip)"
22+
run: |
23+
brew install skiptools/skip/skip || (brew update && brew install skiptools/skip/skip)
24+
skip android sdk install --version ${{ matrix.swift }}
25+
# https://github.com/swiftlang/swift-driver/pull/1879
26+
echo 'ANDROID_NDK_ROOT=""' >> $GITHUB_ENV
27+
- name: "Test Swift Package on Android (Skip)"
28+
uses: reactivecircus/android-emulator-runner@v2
29+
with:
30+
api-level: 28
31+
arch: x86_64
32+
script: runall.sh
33+

.gitignore

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## User settings
6+
xcuserdata/
7+
8+
xcodebuild*.log
9+
10+
java_pid*.hprof
11+
12+
.*.swp
13+
.DS_Store
14+
15+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
16+
*.xcscmblueprint
17+
*.xccheckout
18+
19+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
20+
build/
21+
DerivedData/
22+
.android/
23+
.kotlin/
24+
*.moved-aside
25+
*.pbxuser
26+
!default.pbxuser
27+
*.mode1v3
28+
!default.mode1v3
29+
*.mode2v3
30+
!default.mode2v3
31+
*.perspectivev3
32+
!default.perspectivev3
33+
34+
## Obj-C/Swift specific
35+
*.hmap
36+
37+
## App packaging
38+
*.ipa
39+
*.dSYM.zip
40+
*.dSYM
41+
42+
## Playgrounds
43+
timeline.xctimeline
44+
playground.xcworkspace
45+
46+
# Swift Package Manager
47+
#
48+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
49+
Packages/
50+
Package.pins
51+
Package.resolved
52+
*.xcodeproj
53+
54+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
55+
# hence it is not needed unless you have added a package configuration file to your project
56+
.swiftpm
57+
58+
.build/
59+
60+
# CocoaPods
61+
#
62+
# We recommend against adding the Pods directory to your .gitignore. However
63+
# you should judge for yourself, the pros and cons are mentioned at:
64+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
65+
#
66+
# Pods/
67+
#
68+
# Add this line if you want to avoid checking in source code from the Xcode workspace
69+
# *.xcworkspace
70+
71+
# Carthage
72+
#
73+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
74+
# Carthage/Checkouts
75+
76+
Carthage/Build/
77+
78+
# Accio dependency management
79+
Dependencies/
80+
.accio/
81+
82+
# fastlane
83+
#
84+
# It is recommended to not store the screenshots in the git repo.
85+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
86+
# For more information about the recommended setup visit:
87+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
88+
89+
fastlane/report.xml
90+
fastlane/Preview.html
91+
fastlane/screenshots/**/*.png
92+
fastlane/test_output
93+
94+
# Code Injection
95+
#
96+
# After new code Injection tools there's a generated folder /iOSInjectionProject
97+
# https://github.com/johnno1962/injectionforxcode
98+
99+
iOSInjectionProject/
100+
101+
102+
103+
# Ignore Gradle project-specific cache directory
104+
.gradle
105+
106+
# Ignore Gradle build output directory
107+
build
108+
109+
# gradle properties
110+
local.properties

android-fetchurl/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

android-fetchurl/Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// swift-tools-version: 5.10
2+
import PackageDescription
3+
4+
let package = Package(name: "android-fetchurl",
5+
platforms: [.iOS(.v14), .macOS(.v12)],
6+
targets: [.executableTarget(name: "android-fetchurl")]
7+
)

android-fetchurl/Sources/main.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/// This example provides a `installSystemCertificates()` function that will
2+
/// locate the standard Android certificate directories and assemble all the contents into
3+
/// a single cacerts.pem file that can be loaded by libcurl, which enables
4+
/// `Foundation.URLSession` to load HTTPS sites.
5+
import Foundation
6+
#if canImport(FoundationNetworking)
7+
import FoundationNetworking
8+
#endif
9+
#if canImport(Android)
10+
import Android
11+
#endif
12+
13+
// tell libcurl to use the auto-generated .pem file
14+
try installSystemCertificates()
15+
16+
for arg in CommandLine.arguments.dropFirst() {
17+
do {
18+
let url = URL(string: arg)!
19+
print("Connecting to: \(url)")
20+
// let data = try await URLSession.shared.data(from: url) // not available on Linux/Android
21+
let data = try Data(contentsOf: url)
22+
print("Fetched \(data.count) bytes")
23+
} catch {
24+
print("Error: \(error)")
25+
}
26+
}
27+
28+
/// Collects all the certificate files from the Android certificate store and writes them to a single `cacerts.pem` file that can be used by libcurl.
29+
///
30+
/// See https://android.googlesource.com/platform/frameworks/base/+/8b192b19f264a8829eac2cfaf0b73f6fc188d933%5E%21/#F0
31+
/// See https://github.com/apple/swift-nio-ssl/blob/d1088ebe0789d9eea231b40741831f37ab654b61/Sources/NIOSSL/AndroidCABundle.swift#L30
32+
func installSystemCertificates(fromCertficateFolders certsFolders: [String] = ["/system/etc/security/cacerts", "/apex/com.android.conscrypt/cacerts"]) throws {
33+
//let cacheFolder = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) // file:////.cache/ (unwritable)
34+
let cacheFolder = FileManager.default.temporaryDirectory
35+
let generatedCacertsURL = cacheFolder.appendingPathComponent("cacerts-\(UUID().uuidString).pem")
36+
37+
FileManager.default.createFile(atPath: generatedCacertsURL.path, contents: nil)
38+
let fs = try FileHandle(forWritingTo: generatedCacertsURL)
39+
defer { try? fs.close() }
40+
41+
// write a header
42+
fs.write("""
43+
## Bundle of CA Root Certificates
44+
## Auto-generated on \(Date())
45+
## by aggregating certificates from: \(certsFolders)
46+
47+
""".data(using: .utf8)!)
48+
49+
// Go through each folder and load each certificate file (ending with ".0"),
50+
// and smash them together into a single aggreagate file tha curl can load.
51+
// The .0 files will contain some extra metadata, but libcurl only cares about the
52+
// -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- sections,
53+
// so we can naïvely concatenate them all and libcurl will understand them.
54+
for certsFolder in certsFolders {
55+
let certsFolderURL = URL(fileURLWithPath: certsFolder)
56+
if (try? certsFolderURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) != true { continue }
57+
let certURLs = try FileManager.default.contentsOfDirectory(at: certsFolderURL, includingPropertiesForKeys: [.isRegularFileKey, .isReadableKey])
58+
for certURL in certURLs {
59+
// cartificate files have names like "53a1b57a.0"
60+
if certURL.pathExtension != "0" { continue }
61+
do {
62+
if try certURL.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile == false { continue }
63+
if try certURL.resourceValues(forKeys: [.isReadableKey]).isReadable == false { continue }
64+
try fs.write(contentsOf: try Data(contentsOf: certURL))
65+
} catch {
66+
print("error reading certificate file \(certURL.path): \(error)")
67+
continue
68+
}
69+
}
70+
}
71+
72+
73+
//setenv("URLSessionCertificateAuthorityInfoFile", "INSECURE_SSL_NO_VERIFY", 1) // disables all certificate verification
74+
//setenv("URLSessionCertificateAuthorityInfoFile", "/system/etc/security/cacerts/", 1) // doesn't work for directories
75+
setenv("URLSessionCertificateAuthorityInfoFile", generatedCacertsURL.path, 1)
76+
}

android-fetchurl/run.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh -ex
2+
skip android build --static-swift-stdlib
3+
skip android run android-fetchurl http://example.org https://example.org

runall.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh -ex
2+
for sample in */run.sh; do
3+
cd $(dirname ${sample})
4+
./run.sh
5+
cd -
6+
done

0 commit comments

Comments
 (0)