Skip to content

Commit 7996c1b

Browse files
committed
Merge tag '4.10.1/9228' into main-4.10.1-9228
2 parents 4a21323 + 26675e1 commit 7996c1b

File tree

14 files changed

+171
-54
lines changed

14 files changed

+171
-54
lines changed

ProtonMail/ProtonMail/Supporting Files/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
<key>CFBundlePackageType</key>
5555
<string>APPL</string>
5656
<key>CFBundleShortVersionString</key>
57-
<string>4.10.0</string>
57+
<string>4.10.1</string>
5858
<key>CFBundleSignature</key>
5959
<string>????</string>
6060
<key>CFBundleURLTypes</key>

ProtonMail/ProtonMail/Supporting Files/InfoDev.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
<key>CFBundlePackageType</key>
5555
<string>APPL</string>
5656
<key>CFBundleShortVersionString</key>
57-
<string>4.10.0</string>
57+
<string>4.10.1</string>
5858
<key>CFBundleSignature</key>
5959
<string>????</string>
6060
<key>CFBundleURLTypes</key>

ProtonMail/ProtonMail/Utilities/APP/UseCase/CheckProtonServerStatusUseCase.swift

+3-10
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,14 @@ enum ServerStatus {
3030
}
3131

3232
final class CheckProtonServerStatus: CheckProtonServerStatusUseCase {
33-
private var protonPingRoute: URL {
34-
// swiftlint:disable:next force_unwrapping
35-
URL(string: "\(dependencies.doh.getCurrentlyUsedHostUrl())/core/v4/tests/ping")!
36-
}
37-
3833
private let dependencies: Dependencies
3934

4035
init(dependencies: Dependencies = Dependencies()) {
4136
self.dependencies = dependencies
4237
}
4338

4439
func execute() async -> ServerStatus {
45-
let isProtonPingSuccessful = await isPingSuccessful(url: protonPingRoute)
40+
let isProtonPingSuccessful = await isPingSuccessful()
4641
let isInternetAvailable = dependencies.internetConnectionStatus.status.isConnected
4742

4843
switch (isProtonPingSuccessful, isInternetAvailable) {
@@ -55,10 +50,8 @@ final class CheckProtonServerStatus: CheckProtonServerStatusUseCase {
5550
}
5651
}
5752

58-
private func isPingSuccessful(url: URL) async -> Bool {
59-
var request = URLRequest(url: url, timeoutInterval: 3)
60-
request.httpMethod = "HEAD"
61-
53+
private func isPingSuccessful() async -> Bool {
54+
let request = PingRequestHelper.protonServer.urlRequest(doh: dependencies.doh)
6255
do {
6356
let response: URLResponse = try await dependencies.session.data(for: request).1
6457
return (response as? HTTPURLResponse)?.statusCode == 200

ProtonMail/ProtonMail/Utilities/APP_share/ConnectionStatus/InternetConnectionStatusProvider.swift

+41-21
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ final class InternetConnectionStatusProvider: InternetConnectionStatusProviderPr
5151
private let pathMonitor: ConnectionMonitor
5252
private let session: URLSessionProtocol
5353
private let notificationCenter: NotificationCenter
54+
private let doh: DoHInterface
5455
private let monitorQueue = DispatchQueue(label: "me.proton.mail.connection.status.monitor", qos: .userInitiated)
5556

5657
private let delegatesStore: NSHashTable<AnyObject> = NSHashTable.weakObjects()
@@ -80,11 +81,13 @@ final class InternetConnectionStatusProvider: InternetConnectionStatusProviderPr
8081
init(
8182
connectionMonitor: ConnectionMonitor = NWPathMonitor(),
8283
session: URLSessionProtocol = URLSession.shared,
83-
notificationCenter: NotificationCenter = NotificationCenter.default
84+
notificationCenter: NotificationCenter = NotificationCenter.default,
85+
doh: DoHInterface = BackendConfiguration.shared.doh
8486
) {
8587
self.pathMonitor = connectionMonitor
8688
self.notificationCenter = notificationCenter
8789
self.session = session
90+
self.doh = doh
8891
startObservation()
8992
}
9093

@@ -150,7 +153,7 @@ extension InternetConnectionStatusProvider {
150153
// The reliable way to detect connection status is calling API
151154
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
152155
self.log(message: "Check connection when vpn is enabled")
153-
self.status = self.hasConnectionWhenVPNISEnabled() ? .connected : .notConnected
156+
self.checkConnectionWhenVPNIsEnabled()
154157
}
155158
return
156159
} else if path.usesInterfaceType(.wifi) {
@@ -230,26 +233,43 @@ extension InternetConnectionStatusProvider {
230233
pathMonitor.cancel()
231234
}
232235

233-
private func hasConnectionWhenVPNISEnabled() -> Bool {
234-
guard let url = URL(string: "https://status.proton.me") else {
235-
PMAssertionFailure("wrong url")
236-
return false
237-
}
238-
var request = URLRequest(url: url, timeoutInterval: 40)
239-
request.httpMethod = "HEAD"
240-
let semaphore = DispatchSemaphore(value: 0)
241-
var isSuccess = true
242-
session.dataTask(withRequest: request) { [weak self] _, _, error in
243-
if let error = error {
244-
self?.log(message: "Ping API failed, \(error)", isError: true)
245-
isSuccess = false
246-
} else {
247-
self?.log(message: "Ping API success")
236+
private func checkConnectionWhenVPNIsEnabled() {
237+
Task {
238+
let hasConnection: Bool
239+
240+
defer {
241+
monitorQueue.async {
242+
self.log(message: "Update status according to ping result", isError: false)
243+
self.status = hasConnection ? .connected : .notConnected
244+
}
248245
}
249-
semaphore.signal()
250-
}.resume()
251-
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
252-
return isSuccess
246+
let tooManyRedirectionsError = -1_007
247+
do {
248+
249+
_ = try await session.data(for: PingRequestHelper.protonServer.urlRequest(timeout: 40, doh: doh))
250+
hasConnection = true
251+
return
252+
} catch {
253+
log(message: "Ping proton server failed: \(error)", isError: true)
254+
if error.bestShotAtReasonableErrorCode == tooManyRedirectionsError {
255+
hasConnection = true
256+
return
257+
}
258+
}
259+
260+
do {
261+
_ = try await session.data(for: PingRequestHelper.protonStatus.urlRequest(timeout: 40, doh: doh))
262+
hasConnection = true
263+
return
264+
} catch {
265+
log(message: "Ping proton status page failed: \(error)", isError: true)
266+
if error.bestShotAtReasonableErrorCode == tooManyRedirectionsError {
267+
hasConnection = true
268+
return
269+
}
270+
}
271+
hasConnection = false
272+
}
253273
}
254274

255275
#if DEBUG
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2023 Proton Technologies AG
2+
//
3+
// This file is part of Proton Mail.
4+
//
5+
// Proton Mail is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// Proton Mail is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
17+
18+
import Foundation
19+
import ProtonCoreDoh
20+
21+
enum PingRequestHelper {
22+
case protonServer, protonStatus
23+
24+
func urlRequest(timeout: TimeInterval = 3, doh: DoHInterface = BackendConfiguration.shared.doh) -> URLRequest {
25+
switch self {
26+
case .protonServer:
27+
let serverLink = "\(doh.getCurrentlyUsedHostUrl())/core/v4/tests/ping"
28+
// swiftlint:disable:next force_unwrapping
29+
let url = URL(string: serverLink)!
30+
var request = URLRequest(url: url, timeoutInterval: timeout)
31+
request.httpMethod = "HEAD"
32+
return request
33+
case .protonStatus:
34+
// swiftlint:disable:next force_unwrapping
35+
let statusPageURL = URL(string: Link.protonStatusPage)!
36+
var request = URLRequest(url: statusPageURL, timeoutInterval: timeout)
37+
request.httpMethod = "HEAD"
38+
return request
39+
}
40+
}
41+
}

ProtonMail/ProtonMailTests/ProtonMail/Utilities/InternetConnectionStatusProviderTests.swift

+76-13
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class InternetConnectionStatusProviderTests: XCTestCase {
8484
XCTAssertEqual(connectionStatusReceiver.connectionStatusHasChangedStub.callCounter, 3)
8585
}
8686

87-
func testHasConnection_whenConnectedViaVPN_andPingFails_itShouldReturnNotConnected() {
87+
func testHasConnection_whenConnectedViaVPN_andBothPingTargetsAreFailed_itShouldReturnNotConnected() {
8888
sut.register(receiver: connectionStatusReceiver, fireWhenRegister: false)
8989

9090
let expectation1 = expectation(description: "status updated")
@@ -98,22 +98,25 @@ class InternetConnectionStatusProviderTests: XCTestCase {
9898
let expected: [NWInterface.InterfaceType] = [.wifi, .other]
9999
return expected.contains(interface)
100100
}
101-
session.dataTaskStub.bodyIs { _, request, handler in
101+
session.dataStub.bodyIs { call, request in
102+
let error = NSError(domain: "pm.test", code: -999)
102103
guard let link = request.url?.absoluteString else {
103104
XCTFail("Link shouldn't be nil")
104-
return MockURLSessionDataTaskProtocol()
105+
throw error
105106
}
106-
XCTAssertEqual(link, "https://status.proton.me")
107-
let error = NSError(domain: "pm.test", code: -999)
108-
handler(nil, nil, error)
109-
return MockURLSessionDataTaskProtocol()
107+
if call == 1 {
108+
XCTAssertEqual(link, "https://mail-api.proton.me/core/v4/tests/ping")
109+
} else {
110+
XCTAssertEqual(link, "https://status.proton.me")
111+
}
112+
throw error
110113
}
111114

112115
updateConnection(isConnected: true, interfaces: [.other])
113116
wait(for: [expectation1], timeout: 5)
114117
}
115118

116-
func testHasConnection_whenConnectedViaVPN_andPingSucceeds_itShouldReturnConnected() {
119+
func testHasConnection_whenConnectedViaVPN_andServerPingSucceeds_itShouldReturnConnected() {
117120
sut.register(receiver: connectionStatusReceiver, fireWhenRegister: false)
118121
let expectation1 = expectation(description: "status updated")
119122
connectionStatusReceiver.connectionStatusHasChangedStub.bodyIs { _, newStatus in
@@ -126,19 +129,79 @@ class InternetConnectionStatusProviderTests: XCTestCase {
126129
let expected: [NWInterface.InterfaceType] = [.wifi, .other]
127130
return expected.contains(interface)
128131
}
129-
session.dataTaskStub.bodyIs { _, request, handler in
132+
session.dataStub.bodyIs { call, request in
130133
guard let link = request.url?.absoluteString else {
131134
XCTFail("Link shouldn't be nil")
132-
return MockURLSessionDataTaskProtocol()
135+
throw NSError(domain: "pm.test", code: -999)
133136
}
134-
XCTAssertEqual(link, "https://status.proton.me")
135-
handler(nil, nil, nil)
136-
return MockURLSessionDataTaskProtocol()
137+
XCTAssertEqual(link, "https://mail-api.proton.me/core/v4/tests/ping")
138+
return (Data(), URLResponse())
137139
}
138140

139141
updateConnection(isConnected: true, interfaces: [.other, .wifi])
140142
wait(for: [expectation1], timeout: 5)
141143
}
144+
145+
func testHasConnection_whenConnectedViaVPN_serverPingFailedButStatusPageSuccess_itShouldReturnConnected() {
146+
sut.register(receiver: connectionStatusReceiver, fireWhenRegister: false)
147+
148+
let expectation1 = expectation(description: "status updated")
149+
connectionStatusReceiver.connectionStatusHasChangedStub.bodyIs { _, newStatus in
150+
XCTAssertEqual(newStatus, .connected)
151+
expectation1.fulfill()
152+
}
153+
154+
mockNWPath.pathStatusStub.fixture = .satisfied
155+
mockNWPath.usesInterfaceTypeStub.bodyIs { _, interface in
156+
let expected: [NWInterface.InterfaceType] = [.wifi, .other]
157+
return expected.contains(interface)
158+
}
159+
session.dataStub.bodyIs { call, request in
160+
let error = NSError(domain: "pm.test", code: -999)
161+
guard let link = request.url?.absoluteString else {
162+
XCTFail("Link shouldn't be nil")
163+
throw error
164+
}
165+
if call == 1 {
166+
XCTAssertEqual(link, "https://mail-api.proton.me/core/v4/tests/ping")
167+
throw error
168+
} else {
169+
XCTAssertEqual(link, "https://status.proton.me")
170+
return (Data(), URLResponse())
171+
}
172+
}
173+
174+
updateConnection(isConnected: true, interfaces: [.other])
175+
wait(for: [expectation1], timeout: 5)
176+
}
177+
178+
func testHasConnection_whenConnectedViaVPN_receiveTooManyRedirection_itShouldReturnConnected() {
179+
sut.register(receiver: connectionStatusReceiver, fireWhenRegister: false)
180+
181+
let expectation1 = expectation(description: "status updated")
182+
connectionStatusReceiver.connectionStatusHasChangedStub.bodyIs { _, newStatus in
183+
XCTAssertEqual(newStatus, .connected)
184+
expectation1.fulfill()
185+
}
186+
187+
mockNWPath.pathStatusStub.fixture = .satisfied
188+
mockNWPath.usesInterfaceTypeStub.bodyIs { _, interface in
189+
let expected: [NWInterface.InterfaceType] = [.wifi, .other]
190+
return expected.contains(interface)
191+
}
192+
session.dataStub.bodyIs { call, request in
193+
let error = NSError(domain: "pm.test", code: -1007)
194+
guard let link = request.url?.absoluteString else {
195+
XCTFail("Link shouldn't be nil")
196+
throw error
197+
}
198+
XCTAssertEqual(link, "https://mail-api.proton.me/core/v4/tests/ping")
199+
throw error
200+
}
201+
202+
updateConnection(isConnected: true, interfaces: [.other])
203+
wait(for: [expectation1], timeout: 5)
204+
}
142205
}
143206

144207
extension InternetConnectionStatusProviderTests {

ProtonMail/ProtonMailTests/Supporting Files/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>BNDL</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>4.10.0</string>
18+
<string>4.10.1</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>

ProtonMail/ProtonMailUITests/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>BNDL</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>4.10.0</string>
18+
<string>4.10.1</string>
1919
<key>CFBundleVersion</key>
2020
<string>1</string>
2121
</dict>

ProtonMail/PushService/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundlePackageType</key>
1818
<string>XPC!</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>4.10.0</string>
20+
<string>4.10.1</string>
2121
<key>CFBundleVersion</key>
2222
<string>Debug</string>
2323
<key>NSExtension</key>

ProtonMail/PushService/InfoDev.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundlePackageType</key>
1818
<string>XPC!</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>4.10.0</string>
20+
<string>4.10.1</string>
2121
<key>CFBundleVersion</key>
2222
<string>Debug</string>
2323
<key>NSExtension</key>

ProtonMail/Share/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundlePackageType</key>
1818
<string>XPC!</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>4.10.0</string>
20+
<string>4.10.1</string>
2121
<key>CFBundleVersion</key>
2222
<string>Debug</string>
2323
<key>LSSupportsOpeningDocumentsInPlace</key>

ProtonMail/Share/InfoDev.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundlePackageType</key>
1818
<string>XPC!</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>4.10.0</string>
20+
<string>4.10.1</string>
2121
<key>CFBundleVersion</key>
2222
<string>Debug</string>
2323
<key>LSSupportsOpeningDocumentsInPlace</key>

ProtonMail/Siri/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundlePackageType</key>
1818
<string>XPC!</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>4.10.0</string>
20+
<string>4.10.1</string>
2121
<key>CFBundleVersion</key>
2222
<string>Debug</string>
2323
<key>NSExtension</key>

ProtonMail/Siri/InfoDev.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CFBundlePackageType</key>
1818
<string>XPC!</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>4.10.0</string>
20+
<string>4.10.1</string>
2121
<key>CFBundleVersion</key>
2222
<string>Debug</string>
2323
<key>NSExtension</key>

0 commit comments

Comments
 (0)