From 0cc927ef5f0f7076a6d486d666d78483f1d71c54 Mon Sep 17 00:00:00 2001 From: Chace Daniels Date: Fri, 4 Aug 2023 10:23:42 -0700 Subject: [PATCH] fix(cookies): hide httpOnly cookies from client --- .../src/main/assets/native-bridge.js | 4 +- .../getcapacitor/plugin/CapacitorCookies.java | 46 +++++++++++++------ core/native-bridge.ts | 3 +- .../Plugins/CapacitorCookieManager.swift | 7 ++- .../Capacitor/assets/native-bridge.js | 4 +- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index 898d9d6386..fdd5a7e6c1 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -349,6 +349,7 @@ var nativeBridge = (function (exports) { if (doPatchCookies) { Object.defineProperty(document, 'cookie', { get: function () { + var _a, _b, _c; if (platform === 'ios') { // Use prompt to synchronously get cookies. // https://stackoverflow.com/questions/29249132/wkwebview-complex-communication-between-javascript-native-code/49474323#49474323 @@ -359,7 +360,8 @@ var nativeBridge = (function (exports) { return res; } else if (typeof win.CapacitorCookiesAndroidInterface !== 'undefined') { - return win.CapacitorCookiesAndroidInterface.getCookies(); + // return original document.cookie since Android does not support filtering of `httpOnly` cookies + return (_c = (_b = (_a = win.CapacitorCookiesDescriptor) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(document)) !== null && _c !== void 0 ? _c : ''; } }, set: function (val) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookies.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookies.java index 8d92d62313..95a27cb0fc 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookies.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookies.java @@ -1,16 +1,17 @@ package com.getcapacitor.plugin; import android.webkit.JavascriptInterface; -import androidx.annotation.Nullable; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; import com.getcapacitor.PluginCall; import com.getcapacitor.PluginConfig; import com.getcapacitor.PluginMethod; import com.getcapacitor.annotation.CapacitorPlugin; +import java.io.UnsupportedEncodingException; import java.net.CookieHandler; import java.net.HttpCookie; -import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; @CapacitorPlugin public class CapacitorCookies extends Plugin { @@ -31,12 +32,6 @@ public boolean isEnabled() { return pluginConfig.getBoolean("enabled", false); } - @JavascriptInterface - public String getCookies() { - String cookieString = cookieManager.getCookieString(null); - return (null == cookieString) ? "" : cookieString; - } - @JavascriptInterface public void setCookie(String domain, String action) { cookieManager.setCookie(domain, action); @@ -44,13 +39,34 @@ public void setCookie(String domain, String action) { @PluginMethod public void getCookies(PluginCall call) { - String url = call.getString("url"); - JSObject cookiesMap = new JSObject(); - HttpCookie[] cookies = cookieManager.getCookies(url); - for (HttpCookie cookie : cookies) { - cookiesMap.put(cookie.getName(), cookie.getValue()); - } - call.resolve(cookiesMap); + this.bridge.eval( + "document.cookie", + value -> { + String cookies = value.substring(1, value.length() - 1); + String[] cookieArray = cookies.split(";"); + + JSObject cookieMap = new JSObject(); + + for (String cookie : cookieArray) { + if (cookie.length() > 0) { + String[] keyValue = cookie.split("=", 2); + + if (keyValue.length == 2) { + String key = keyValue[0].trim(); + String val = keyValue[1].trim(); + try { + key = URLDecoder.decode(keyValue[0].trim(), StandardCharsets.UTF_8.name()); + val = URLDecoder.decode(keyValue[1].trim(), StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException ignored) {} + + cookieMap.put(key, val); + } + } + } + + call.resolve(cookieMap); + } + ); } @PluginMethod diff --git a/core/native-bridge.ts b/core/native-bridge.ts index a33a151915..de4faf130c 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -394,7 +394,8 @@ const initBridge = (w: any): void => { } else if ( typeof win.CapacitorCookiesAndroidInterface !== 'undefined' ) { - return win.CapacitorCookiesAndroidInterface.getCookies(); + // return original document.cookie since Android does not support filtering of `httpOnly` cookies + return win.CapacitorCookiesDescriptor?.get?.call(document) ?? ''; } }, set: function (val) { diff --git a/ios/Capacitor/Capacitor/Plugins/CapacitorCookieManager.swift b/ios/Capacitor/Capacitor/Plugins/CapacitorCookieManager.swift index 1c61477c87..782c245b0b 100644 --- a/ios/Capacitor/Capacitor/Plugins/CapacitorCookieManager.swift +++ b/ios/Capacitor/Capacitor/Plugins/CapacitorCookieManager.swift @@ -77,7 +77,9 @@ public class CapacitorCookieManager { let jar = HTTPCookieStorage.shared if let cookies = jar.cookies(for: url) { for cookie in cookies { - cookiesMap[cookie.name] = cookie.value + if !cookie.isHTTPOnly { + cookiesMap[cookie.name] = cookie.value + } } } return cookiesMap @@ -88,7 +90,8 @@ public class CapacitorCookieManager { let jar = HTTPCookieStorage.shared guard let url = self.getServerUrl() else { return "" } guard let cookies = jar.cookies(for: url) else { return "" } - return cookies.map({"\($0.name)=\($0.value)"}).joined(separator: "; ") + let filteredCookies = cookies.filter { !$0.isHTTPOnly } + return filteredCookies.map({"\($0.name)=\($0.value)"}).joined(separator: "; ") } public func deleteCookie(_ url: URL, _ key: String) { diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index 898d9d6386..fdd5a7e6c1 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -349,6 +349,7 @@ var nativeBridge = (function (exports) { if (doPatchCookies) { Object.defineProperty(document, 'cookie', { get: function () { + var _a, _b, _c; if (platform === 'ios') { // Use prompt to synchronously get cookies. // https://stackoverflow.com/questions/29249132/wkwebview-complex-communication-between-javascript-native-code/49474323#49474323 @@ -359,7 +360,8 @@ var nativeBridge = (function (exports) { return res; } else if (typeof win.CapacitorCookiesAndroidInterface !== 'undefined') { - return win.CapacitorCookiesAndroidInterface.getCookies(); + // return original document.cookie since Android does not support filtering of `httpOnly` cookies + return (_c = (_b = (_a = win.CapacitorCookiesDescriptor) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(document)) !== null && _c !== void 0 ? _c : ''; } }, set: function (val) {