API requests may be sent without custom User-Agent header in certain scenarios
API requests (including /api/v2/languages) can be sent without a custom User-Agent header in certain scenarios, which may cause issues with API rate limiting, analytics, or server-side filtering.
When This Occurs
The versioned() method in CrowdinAPI.swift can fail to set the User-Agent header in two scenarios:
-
Bundle identifier not found: When the SDK is integrated via Swift Package Manager (SPM) instead of CocoaPods, the bundle identifier
"org.cocoapods.CrowdinSDK"may not exist, causing the guard statement to return early without setting the User-Agent. -
macOS not handled: The code only handles iOS, tvOS, and watchOS platforms. When running on macOS, the
#ifconditions don't match, so the User-Agent header is never set.
Affected Code
func versioned(_ headers: [String: String]?) -> [String: String] {
var result = headers ?? [:]
guard let bundle = Bundle(identifier: "org.cocoapods.CrowdinSDK"),
let sdkVersionNumber = bundle.infoDictionary?["CFBundleShortVersionString"] as? String
else { return result } // ⚠️ Returns without User-Agent if bundle not found
#if os(iOS) || os(tvOS)
let systemVersion = "iOS: \(UIDevice.current.systemVersion)"
result["User-Agent"] = "crowdin-ios-sdk/\(sdkVersionNumber) iOS/\(systemVersion)"
#elseif os(watchOS)
let systemVersion = "watchOS: \(WKInterfaceDevice.current().systemVersion)"
result["User-Agent"] = "crowdin-ios-sdk/\(sdkVersionNumber) iOS/\(systemVersion)"
#endif
// ⚠️ No User-Agent set for macOS
return result
}
Location: Sources/CrowdinSDK/CrowdinAPI/CrowdinAPI.swift:215-226
Impact
- All API endpoints using
CrowdinAPI(including/api/v2/languages) may send requests without a custom User-Agent - This affects both async (
cw_get,cw_post, etc.) and sync (cw_getSync,cw_postSync, etc.) methods - Requests will fall back to the system default User-Agent, which may not identify the SDK properly
Suggested Fix
- Add fallback logic to find the bundle using alternative methods (e.g.,
Bundle(for: type(of: self))orBundle.main) - Add macOS support to the platform-specific code
- Ensure a User-Agent is always set, even if the SDK version cannot be determined (e.g., use a default value)
Example Fix Approach
Note: The example code below should be verified and tested before implementation.
func versioned(_ headers: [String: String]?) -> [String: String] {
var result = headers ?? [:]
// Try multiple bundle lookup strategies
var bundle: Bundle?
var sdkVersionNumber: String?
// Try CocoaPods bundle first
if let podBundle = Bundle(identifier: "org.cocoapods.CrowdinSDK"),
let version = podBundle.infoDictionary?["CFBundleShortVersionString"] as? String {
bundle = podBundle
sdkVersionNumber = version
}
// Fallback to SPM bundle
else if let spmBundle = Bundle(for: type(of: self)),
let version = spmBundle.infoDictionary?["CFBundleShortVersionString"] as? String {
bundle = spmBundle
sdkVersionNumber = version
}
let versionString = sdkVersionNumber ?? "unknown"
#if os(iOS) || os(tvOS)
let systemVersion = "iOS: \(UIDevice.current.systemVersion)"
result["User-Agent"] = "crowdin-ios-sdk/\(versionString) iOS/\(systemVersion)"
#elseif os(watchOS)
let systemVersion = "watchOS: \(WKInterfaceDevice.current().systemVersion)"
result["User-Agent"] = "crowdin-ios-sdk/\(versionString) iOS/\(systemVersion)"
#elseif os(macOS)
let systemVersion = ProcessInfo.processInfo.operatingSystemVersionString
result["User-Agent"] = "crowdin-ios-sdk/\(versionString) macOS/\(systemVersion)"
#endif
return result
}
Testing
- Test with CocoaPods integration
- Test with Swift Package Manager integration
- Test on macOS platform
- Verify User-Agent header is always present in network requests