react-native-security-suite 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/android/build.gradle +46 -28
  4. package/android/src/main/AndroidManifest.xml +47 -0
  5. package/android/src/main/{AndroidManifestDeprecated.xml → AndroidManifestNew.xml} +1 -2
  6. package/android/src/main/java/com/securitysuite/NetworkLogger.java +25 -0
  7. package/android/src/main/java/com/securitysuite/Sslpinning.java +39 -54
  8. package/android/src/main/java/com/securitysuite/api/BodyDecoder.kt +35 -0
  9. package/android/src/main/java/com/securitysuite/api/Chucker.kt +108 -0
  10. package/android/src/main/java/com/securitysuite/api/ChuckerCollector.kt +129 -0
  11. package/android/src/main/java/com/securitysuite/api/ChuckerInterceptor.kt +280 -0
  12. package/android/src/main/java/com/securitysuite/api/ExportFormat.kt +12 -0
  13. package/android/src/main/java/com/securitysuite/api/RetentionManager.kt +109 -0
  14. package/android/src/main/java/com/securitysuite/internal/data/entity/HttpHeader.kt +8 -0
  15. package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransaction.kt +344 -0
  16. package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransactionTuple.kt +62 -0
  17. package/android/src/main/java/com/securitysuite/internal/data/har/Har.kt +13 -0
  18. package/android/src/main/java/com/securitysuite/internal/data/har/Log.kt +24 -0
  19. package/android/src/main/java/com/securitysuite/internal/data/har/log/Browser.kt +11 -0
  20. package/android/src/main/java/com/securitysuite/internal/data/har/log/Creator.kt +11 -0
  21. package/android/src/main/java/com/securitysuite/internal/data/har/log/Entry.kt +49 -0
  22. package/android/src/main/java/com/securitysuite/internal/data/har/log/Page.kt +14 -0
  23. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Cache.kt +12 -0
  24. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Header.kt +17 -0
  25. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Request.kt +35 -0
  26. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Response.kt +33 -0
  27. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Timings.kt +25 -0
  28. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/cache/SecondaryRequest.kt +13 -0
  29. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/PostData.kt +20 -0
  30. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/QueryString.kt +23 -0
  31. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/postdata/Params.kt +13 -0
  32. package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/response/Content.kt +30 -0
  33. package/android/src/main/java/com/securitysuite/internal/data/har/log/page/PageTimings.kt +11 -0
  34. package/android/src/main/java/com/securitysuite/internal/data/model/DialogData.kt +8 -0
  35. package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionDatabaseRepository.kt +60 -0
  36. package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionRepository.kt +33 -0
  37. package/android/src/main/java/com/securitysuite/internal/data/repository/RepositoryProvider.kt +38 -0
  38. package/android/src/main/java/com/securitysuite/internal/data/room/ChuckerDatabase.kt +22 -0
  39. package/android/src/main/java/com/securitysuite/internal/data/room/HttpTransactionDao.kt +53 -0
  40. package/android/src/main/java/com/securitysuite/internal/support/BitmapUtils.kt +45 -0
  41. package/android/src/main/java/com/securitysuite/internal/support/CacheDirectoryProvider.kt +11 -0
  42. package/android/src/main/java/com/securitysuite/internal/support/ChessboardDrawable.kt +76 -0
  43. package/android/src/main/java/com/securitysuite/internal/support/ChuckerFileProvider.kt +9 -0
  44. package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseJobIntentServiceReceiver.kt +14 -0
  45. package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseService.kt +32 -0
  46. package/android/src/main/java/com/securitysuite/internal/support/ContextExt.kt +22 -0
  47. package/android/src/main/java/com/securitysuite/internal/support/DepletingSource.kt +37 -0
  48. package/android/src/main/java/com/securitysuite/internal/support/FileFactory.kt +30 -0
  49. package/android/src/main/java/com/securitysuite/internal/support/FileSaver.kt +41 -0
  50. package/android/src/main/java/com/securitysuite/internal/support/FormatUtils.kt +117 -0
  51. package/android/src/main/java/com/securitysuite/internal/support/FormattedUrl.kt +76 -0
  52. package/android/src/main/java/com/securitysuite/internal/support/HarUtils.kt +28 -0
  53. package/android/src/main/java/com/securitysuite/internal/support/HttpUrlUtils.kt +11 -0
  54. package/android/src/main/java/com/securitysuite/internal/support/JsonConverter.kt +19 -0
  55. package/android/src/main/java/com/securitysuite/internal/support/LimitingSource.kt +22 -0
  56. package/android/src/main/java/com/securitysuite/internal/support/LiveDataUtils.kt +68 -0
  57. package/android/src/main/java/com/securitysuite/internal/support/Logger.kt +43 -0
  58. package/android/src/main/java/com/securitysuite/internal/support/NotificationHelper.kt +149 -0
  59. package/android/src/main/java/com/securitysuite/internal/support/OkHttpUtils.kt +86 -0
  60. package/android/src/main/java/com/securitysuite/internal/support/OkioUtils.kt +34 -0
  61. package/android/src/main/java/com/securitysuite/internal/support/PlainTextDecoder.kt +30 -0
  62. package/android/src/main/java/com/securitysuite/internal/support/ReportingSink.kt +114 -0
  63. package/android/src/main/java/com/securitysuite/internal/support/RequestProcessor.kt +102 -0
  64. package/android/src/main/java/com/securitysuite/internal/support/ResponseProcessor.kt +170 -0
  65. package/android/src/main/java/com/securitysuite/internal/support/SearchHighlightUtil.kt +80 -0
  66. package/android/src/main/java/com/securitysuite/internal/support/Sharable.kt +86 -0
  67. package/android/src/main/java/com/securitysuite/internal/support/SpanTextUtil.kt +202 -0
  68. package/android/src/main/java/com/securitysuite/internal/support/TeeSource.kt +68 -0
  69. package/android/src/main/java/com/securitysuite/internal/support/TransactionCurlCommandSharable.kt +46 -0
  70. package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsHarSharable.kt +11 -0
  71. package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsSharable.kt +73 -0
  72. package/android/src/main/java/com/securitysuite/internal/support/TransactionDiffCallback.kt +26 -0
  73. package/android/src/main/java/com/securitysuite/internal/support/TransactionListDetailsSharable.kt +23 -0
  74. package/android/src/main/java/com/securitysuite/internal/ui/BaseChuckerActivity.kt +35 -0
  75. package/android/src/main/java/com/securitysuite/internal/ui/MainActivity.kt +375 -0
  76. package/android/src/main/java/com/securitysuite/internal/ui/MainViewModel.kt +47 -0
  77. package/android/src/main/java/com/securitysuite/internal/ui/transaction/PayloadType.kt +6 -0
  78. package/android/src/main/java/com/securitysuite/internal/ui/transaction/ProtocolResources.kt +14 -0
  79. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionActivity.kt +186 -0
  80. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionAdapter.kt +139 -0
  81. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionOverviewFragment.kt +100 -0
  82. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPagerAdapter.kt +29 -0
  83. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadAdapter.kt +269 -0
  84. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadFragment.kt +529 -0
  85. package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionViewModel.kt +69 -0
  86. package/android/src/main/res/A.java +12 -0
  87. package/android/src/main/res/color/chucker_fab_background_colour.xml +5 -0
  88. package/android/src/main/res/drawable/chucker_empty_payload.xml +10 -0
  89. package/android/src/main/res/drawable/chucker_ic_arrow_down.xml +10 -0
  90. package/android/src/main/res/drawable/chucker_ic_copy.xml +12 -0
  91. package/android/src/main/res/drawable/chucker_ic_decoded_url_white.xml +10 -0
  92. package/android/src/main/res/drawable/chucker_ic_delete_white.xml +9 -0
  93. package/android/src/main/res/drawable/chucker_ic_encoded_url_white.xml +11 -0
  94. package/android/src/main/res/drawable/chucker_ic_graphql.xml +27 -0
  95. package/android/src/main/res/drawable/chucker_ic_http.xml +10 -0
  96. package/android/src/main/res/drawable/chucker_ic_https.xml +9 -0
  97. package/android/src/main/res/drawable/chucker_ic_launcher_foreground.xml +14 -0
  98. package/android/src/main/res/drawable/chucker_ic_save_white.xml +9 -0
  99. package/android/src/main/res/drawable/chucker_ic_search_white.xml +9 -0
  100. package/android/src/main/res/drawable/chucker_ic_share_white.xml +9 -0
  101. package/android/src/main/res/drawable/chucker_ic_transaction_notification.xml +9 -0
  102. package/android/src/main/res/layout/activity_main.xml +83 -0
  103. package/android/src/main/res/layout/activity_transaction.xml +48 -0
  104. package/android/src/main/res/layout/fragment_transaction_overview.xml +365 -0
  105. package/android/src/main/res/layout/fragment_transaction_payload.xml +132 -0
  106. package/android/src/main/res/layout/list_item_transaction.xml +122 -0
  107. package/android/src/main/res/layout/transaction_item_body_line.xml +13 -0
  108. package/android/src/main/res/layout/transaction_item_copy.xml +19 -0
  109. package/android/src/main/res/layout/transaction_item_headers.xml +12 -0
  110. package/android/src/main/res/layout/transaction_item_image.xml +16 -0
  111. package/android/src/main/res/menu/chucker_transaction.xml +46 -0
  112. package/android/src/main/res/menu/chucker_transactions_list.xml +41 -0
  113. package/android/src/main/res/mipmap-anydpi-v26/chucker_ic_launcher.xml +5 -0
  114. package/android/src/main/res/mipmap-hdpi/chucker_ic_launcher.png +0 -0
  115. package/android/src/main/res/mipmap-xhdpi/chucker_ic_launcher.png +0 -0
  116. package/android/src/main/res/mipmap-xxhdpi/chucker_ic_launcher.png +0 -0
  117. package/android/src/main/res/mipmap-xxxhdpi/chucker_ic_launcher.png +0 -0
  118. package/android/src/main/res/values/chucker_ic_launcher_background.xml +4 -0
  119. package/android/src/main/res/values/colors.xml +38 -0
  120. package/android/src/main/res/values/dimens.xml +10 -0
  121. package/android/src/main/res/values/public.xml +5 -0
  122. package/android/src/main/res/values/strings.xml +77 -0
  123. package/android/src/main/res/values/styles.xml +44 -0
  124. package/android/src/main/res/values-es/strings.xml +75 -0
  125. package/android/src/main/res/values-night/colors.xml +32 -0
  126. package/android/src/main/res/xml/chucker_provider_paths.xml +4 -0
  127. package/ios/SecuritySuite.swift +0 -2
  128. package/ios/SslPinning.swift +0 -26
  129. package/lib/commonjs/helpers.js +1 -1
  130. package/lib/commonjs/helpers.js.map +1 -1
  131. package/lib/commonjs/index.js +23 -40
  132. package/lib/commonjs/index.js.map +1 -1
  133. package/lib/module/helpers.js +3 -1
  134. package/lib/module/helpers.js.map +1 -1
  135. package/lib/module/index.js +21 -33
  136. package/lib/module/index.js.map +1 -1
  137. package/lib/typescript/commonjs/package.json +1 -0
  138. package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -0
  139. package/lib/typescript/{index.d.ts → commonjs/src/index.d.ts} +1 -6
  140. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  141. package/lib/typescript/module/package.json +1 -0
  142. package/lib/typescript/module/src/helpers.d.ts +3 -0
  143. package/lib/typescript/module/src/helpers.d.ts.map +1 -0
  144. package/lib/typescript/module/src/index.d.ts +72 -0
  145. package/lib/typescript/module/src/index.d.ts.map +1 -0
  146. package/package.json +70 -43
  147. package/react-native-security-suite.podspec +23 -15
  148. package/src/helpers.ts +1 -1
  149. package/src/index.tsx +5 -18
  150. package/android/src/main/java/com/securitysuite/AndroidLogger.kt +0 -19
  151. package/android/src/main/java/com/securitysuite/modifier/Base64Decoder.kt +0 -11
  152. package/android/src/main/java/com/securitysuite/modifier/BasicAuthorizationHeaderModifier.kt +0 -16
  153. package/ios/SecuritySuite.xcodeproj/project.pbxproj +0 -293
  154. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  155. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  156. package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/m.navabifar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  157. package/ios/SecuritySuite.xcodeproj/xcuserdata/m.navabifar.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  158. package/lib/typescript/helpers.d.ts.map +0 -1
  159. package/lib/typescript/index.d.ts.map +0 -1
  160. /package/lib/typescript/{helpers.d.ts → commonjs/src/helpers.d.ts} +0 -0
@@ -0,0 +1,117 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import com.securitysuite.internal.data.entity.HttpHeader
4
+ import com.google.gson.JsonParseException
5
+ import com.google.gson.JsonParser
6
+ import org.w3c.dom.Document
7
+ import org.xml.sax.InputSource
8
+ import org.xml.sax.SAXParseException
9
+ import java.io.ByteArrayInputStream
10
+ import java.io.IOException
11
+ import java.io.StringWriter
12
+ import java.io.UnsupportedEncodingException
13
+ import java.net.URLDecoder
14
+ import java.nio.charset.Charset
15
+ import java.util.Locale
16
+ import javax.xml.XMLConstants
17
+ import javax.xml.parsers.DocumentBuilder
18
+ import javax.xml.parsers.DocumentBuilderFactory
19
+ import javax.xml.transform.OutputKeys
20
+ import javax.xml.transform.TransformerException
21
+ import javax.xml.transform.TransformerFactory
22
+ import javax.xml.transform.dom.DOMSource
23
+ import javax.xml.transform.stream.StreamResult
24
+ import kotlin.math.ln
25
+ import kotlin.math.pow
26
+
27
+ internal object FormatUtils {
28
+ private const val SI_MULTIPLE = 1000
29
+ private const val BASE_TWO_MULTIPLE = 1024
30
+
31
+ fun formatHeaders(
32
+ httpHeaders: List<HttpHeader>?,
33
+ withMarkup: Boolean,
34
+ ): String {
35
+ return httpHeaders?.joinToString(separator = "") { header ->
36
+ if (withMarkup) {
37
+ "<b> ${header.name}: </b>${header.value} <br />"
38
+ } else {
39
+ "${header.name}: ${header.value}\n"
40
+ }
41
+ } ?: ""
42
+ }
43
+
44
+ fun formatByteCount(
45
+ bytes: Long,
46
+ si: Boolean,
47
+ ): String {
48
+ val unit = if (si) SI_MULTIPLE else BASE_TWO_MULTIPLE
49
+
50
+ if (bytes < unit) {
51
+ return "$bytes B"
52
+ }
53
+
54
+ val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
55
+ val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
56
+
57
+ return String.format(Locale.US, "%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
58
+ }
59
+
60
+ fun formatJson(json: String): String {
61
+ return try {
62
+ val je = JsonParser.parseString(json)
63
+ JsonConverter.instance.toJson(je)
64
+ } catch (e: JsonParseException) {
65
+ json
66
+ }
67
+ }
68
+
69
+ fun formatXml(xml: String): String {
70
+ return try {
71
+ val documentFactory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
72
+ // This flag is required for security reasons
73
+ documentFactory.isExpandEntityReferences = false
74
+
75
+ val documentBuilder: DocumentBuilder = documentFactory.newDocumentBuilder()
76
+ val inputSource = InputSource(ByteArrayInputStream(xml.toByteArray(Charset.defaultCharset())))
77
+ val document: Document = documentBuilder.parse(inputSource)
78
+
79
+ val domSource = DOMSource(document)
80
+ val writer = StringWriter()
81
+ val result = StreamResult(writer)
82
+
83
+ TransformerFactory.newInstance().apply {
84
+ setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
85
+ }.newTransformer().apply {
86
+ setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
87
+ setOutputProperty(OutputKeys.INDENT, "yes")
88
+ transform(domSource, result)
89
+ }
90
+ writer.toString()
91
+ } catch (ignore: SAXParseException) {
92
+ xml
93
+ } catch (ignore: IOException) {
94
+ xml
95
+ } catch (ignore: TransformerException) {
96
+ xml
97
+ }
98
+ }
99
+
100
+ fun formatUrlEncodedForm(form: String): String {
101
+ return try {
102
+ if (form.isBlank()) {
103
+ return form
104
+ }
105
+ form.split("&").joinToString(separator = "\n") { entry ->
106
+ val keyValue = entry.split("=")
107
+ val key = keyValue[0]
108
+ val value = if (keyValue.size > 1) URLDecoder.decode(keyValue[1], "UTF-8") else ""
109
+ "$key: $value"
110
+ }
111
+ } catch (ignore: IllegalArgumentException) {
112
+ form
113
+ } catch (ignore: UnsupportedEncodingException) {
114
+ form
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,76 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import okhttp3.HttpUrl
4
+
5
+ internal class FormattedUrl private constructor(
6
+ val scheme: String,
7
+ val host: String,
8
+ val port: Int,
9
+ val path: String,
10
+ val query: String,
11
+ ) {
12
+ val pathWithQuery: String
13
+ get() =
14
+ if (query.isBlank()) {
15
+ path
16
+ } else {
17
+ "$path?$query"
18
+ }
19
+
20
+ val url: String
21
+ get() {
22
+ return if (shouldShowPort()) {
23
+ "$scheme://$host:$port$pathWithQuery"
24
+ } else {
25
+ "$scheme://$host$pathWithQuery"
26
+ }
27
+ }
28
+
29
+ private fun shouldShowPort(): Boolean {
30
+ if (scheme == "https" && port == HTTPS_PORT) {
31
+ return false
32
+ }
33
+ if (scheme == "http" && port == HTTP_PORT) {
34
+ return false
35
+ }
36
+ return true
37
+ }
38
+
39
+ companion object {
40
+ private const val HTTPS_PORT = 443
41
+ private const val HTTP_PORT = 80
42
+
43
+ fun fromHttpUrl(
44
+ httpUrl: HttpUrl,
45
+ encoded: Boolean,
46
+ ): FormattedUrl {
47
+ return if (encoded) {
48
+ encodedUrl(httpUrl)
49
+ } else {
50
+ decodedUrl(httpUrl)
51
+ }
52
+ }
53
+
54
+ private fun encodedUrl(httpUrl: HttpUrl): FormattedUrl {
55
+ val path = httpUrl.encodedPathSegments.joinToString("/")
56
+ return FormattedUrl(
57
+ httpUrl.scheme,
58
+ httpUrl.host,
59
+ httpUrl.port,
60
+ if (path.isNotBlank()) "/$path" else "",
61
+ httpUrl.encodedQuery.orEmpty(),
62
+ )
63
+ }
64
+
65
+ private fun decodedUrl(httpUrl: HttpUrl): FormattedUrl {
66
+ val path = httpUrl.pathSegments.joinToString("/")
67
+ return FormattedUrl(
68
+ httpUrl.scheme,
69
+ httpUrl.host,
70
+ httpUrl.port,
71
+ if (path.isNotBlank()) "/$path" else "",
72
+ httpUrl.query.orEmpty(),
73
+ )
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,28 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import com.securitysuite.internal.data.entity.HttpTransaction
4
+ import com.securitysuite.internal.data.har.Har
5
+ import com.securitysuite.internal.data.har.log.Creator
6
+ import kotlinx.coroutines.Dispatchers
7
+ import kotlinx.coroutines.withContext
8
+
9
+ // http://www.softwareishard.com/blog/har-12-spec/
10
+ // https://github.com/ahmadnassri/har-spec/blob/master/versions/1.2.md
11
+ internal object HarUtils {
12
+ suspend fun harStringFromTransactions(
13
+ transactions: List<HttpTransaction>,
14
+ name: String,
15
+ version: String,
16
+ ): String =
17
+ withContext(Dispatchers.Default) {
18
+ JsonConverter.nonNullSerializerInstance
19
+ .toJson(fromHttpTransactions(transactions, Creator(name, version)))
20
+ }
21
+
22
+ internal fun fromHttpTransactions(
23
+ transactions: List<HttpTransaction>,
24
+ creator: Creator,
25
+ ): Har {
26
+ return Har(transactions, creator)
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import okhttp3.HttpUrl
4
+
5
+ private const val PATH_SEGMENTS_DELIMITER = "/"
6
+
7
+ public fun HttpUrl.Builder.addNonBlankPathSegments(candidatePath: String): HttpUrl.Builder =
8
+ apply {
9
+ candidatePath.split(PATH_SEGMENTS_DELIMITER).filter { it.isNotBlank() }
10
+ .forEach { item -> addPathSegment(item) }
11
+ }
@@ -0,0 +1,19 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import com.google.gson.Gson
4
+ import com.google.gson.GsonBuilder
5
+
6
+ internal object JsonConverter {
7
+ val nonNullSerializerInstance: Gson by lazy {
8
+ GsonBuilder()
9
+ .disableHtmlEscaping()
10
+ .setPrettyPrinting()
11
+ .create()
12
+ }
13
+
14
+ val instance: Gson by lazy {
15
+ nonNullSerializerInstance.newBuilder()
16
+ .serializeNulls()
17
+ .create()
18
+ }
19
+ }
@@ -0,0 +1,22 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import okio.Buffer
4
+ import okio.ForwardingSource
5
+ import okio.Source
6
+
7
+ internal class LimitingSource(
8
+ delegate: Source,
9
+ private val bytesCountThreshold: Long,
10
+ ) : ForwardingSource(delegate) {
11
+ private var bytesRead = 0L
12
+ val isThresholdReached get() = bytesRead >= bytesCountThreshold
13
+
14
+ override fun read(
15
+ sink: Buffer,
16
+ byteCount: Long,
17
+ ) = if (!isThresholdReached) {
18
+ super.read(sink, byteCount).also { bytesRead += it }
19
+ } else {
20
+ -1L
21
+ }
22
+ }
@@ -0,0 +1,68 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import android.annotation.SuppressLint
4
+ import androidx.arch.core.executor.ArchTaskExecutor
5
+ import androidx.lifecycle.LiveData
6
+ import androidx.lifecycle.MediatorLiveData
7
+ import java.util.concurrent.Executor
8
+
9
+ internal fun <T1, T2, R> LiveData<T1>.combineLatest(
10
+ other: LiveData<T2>,
11
+ func: (T1, T2) -> R,
12
+ ): LiveData<R> {
13
+ return MediatorLiveData<R>().apply {
14
+ var lastA: T1? = null
15
+ var lastB: T2? = null
16
+
17
+ addSource(this@combineLatest) {
18
+ lastA = it
19
+ val observedB = lastB
20
+ if (it == null && value != null) {
21
+ value = null
22
+ } else if (it != null && observedB != null) {
23
+ value = func(it, observedB)
24
+ }
25
+ }
26
+
27
+ addSource(other) {
28
+ lastB = it
29
+ val observedA = lastA
30
+ if (it == null && value != null) {
31
+ value = null
32
+ } else if (observedA != null && it != null) {
33
+ value = func(observedA, it)
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ internal fun <T1, T2> LiveData<T1>.combineLatest(other: LiveData<T2>): LiveData<Pair<T1, T2>> {
40
+ return combineLatest(other) { a, b -> a to b }
41
+ }
42
+
43
+ // Unlike built-in extension operation is performed on a provided thread pool.
44
+ // This is needed in our case since we compare requests and responses which can be big
45
+ // and result in frame drops.
46
+ internal fun <T> LiveData<T>.distinctUntilChanged(
47
+ executor: Executor = ioExecutor(),
48
+ areEqual: (old: T, new: T) -> Boolean = { old, new -> old == new },
49
+ ): LiveData<T> {
50
+ val distinctMediator = MediatorLiveData<T>()
51
+ var old = uninitializedToken
52
+ distinctMediator.addSource(this) { new ->
53
+ executor.execute {
54
+ @Suppress("UNCHECKED_CAST")
55
+ if (old === uninitializedToken || !areEqual(old as T, new)) {
56
+ old = new
57
+ distinctMediator.postValue(new)
58
+ }
59
+ }
60
+ }
61
+ return distinctMediator
62
+ }
63
+
64
+ private val uninitializedToken: Any? = Any()
65
+
66
+ // It is lesser evil than providing a custom executor.
67
+ @SuppressLint("RestrictedApi")
68
+ private fun ioExecutor() = ArchTaskExecutor.getIOThreadExecutor()
@@ -0,0 +1,43 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import com.securitysuite.api.Chucker
4
+
5
+ internal interface Logger {
6
+ fun info(
7
+ message: String,
8
+ throwable: Throwable? = null,
9
+ )
10
+
11
+ fun warn(
12
+ message: String,
13
+ throwable: Throwable? = null,
14
+ )
15
+
16
+ fun error(
17
+ message: String,
18
+ throwable: Throwable? = null,
19
+ )
20
+
21
+ companion object : Logger {
22
+ override fun info(
23
+ message: String,
24
+ throwable: Throwable?,
25
+ ) {
26
+ Chucker.logger.info(message, throwable)
27
+ }
28
+
29
+ override fun warn(
30
+ message: String,
31
+ throwable: Throwable?,
32
+ ) {
33
+ Chucker.logger.warn(message, throwable)
34
+ }
35
+
36
+ override fun error(
37
+ message: String,
38
+ throwable: Throwable?,
39
+ ) {
40
+ Chucker.logger.error(message, throwable)
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,149 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import android.app.NotificationChannel
4
+ import android.app.NotificationManager
5
+ import android.app.PendingIntent
6
+ import android.content.Context
7
+ import android.content.Intent
8
+ import android.os.Build
9
+ import android.util.LongSparseArray
10
+ import androidx.core.app.NotificationCompat
11
+ import androidx.core.content.ContextCompat
12
+ import com.securitysuite.R
13
+ import com.securitysuite.api.Chucker
14
+ import com.securitysuite.internal.data.entity.HttpTransaction
15
+ import com.securitysuite.internal.ui.BaseChuckerActivity
16
+ import java.util.HashSet
17
+
18
+ internal class NotificationHelper(val context: Context) {
19
+ companion object {
20
+ private const val TRANSACTIONS_CHANNEL_ID = "chucker_transactions"
21
+
22
+ private const val TRANSACTION_NOTIFICATION_ID = 1138
23
+
24
+ private const val BUFFER_SIZE = 10
25
+ private const val INTENT_REQUEST_CODE = 11
26
+ private val transactionBuffer = LongSparseArray<HttpTransaction>()
27
+ private val transactionIdsSet = HashSet<Long>()
28
+
29
+ fun clearBuffer() {
30
+ synchronized(transactionBuffer) {
31
+ transactionBuffer.clear()
32
+ transactionIdsSet.clear()
33
+ }
34
+ }
35
+ }
36
+
37
+ private val notificationManager: NotificationManager =
38
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
39
+
40
+ private val transactionsScreenIntent by lazy {
41
+ PendingIntent.getActivity(
42
+ context,
43
+ TRANSACTION_NOTIFICATION_ID,
44
+ Chucker.getLaunchIntent(context),
45
+ PendingIntent.FLAG_UPDATE_CURRENT or immutableFlag(),
46
+ )
47
+ }
48
+
49
+ init {
50
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
51
+ val transactionsChannel =
52
+ NotificationChannel(
53
+ TRANSACTIONS_CHANNEL_ID,
54
+ context.getString(R.string.chucker_network_notification_category),
55
+ NotificationManager.IMPORTANCE_LOW,
56
+ )
57
+ notificationManager.createNotificationChannels(listOf(transactionsChannel))
58
+ }
59
+ }
60
+
61
+ private fun addToBuffer(transaction: HttpTransaction) {
62
+ if (transaction.id == 0L) {
63
+ // Don't store Transactions with an invalid ID (0).
64
+ // Transaction with an Invalid ID will be shown twice in the notification
65
+ // with both the invalid and the valid ID and we want to avoid this.
66
+ return
67
+ }
68
+ synchronized(transactionBuffer) {
69
+ transactionIdsSet.add(transaction.id)
70
+ transactionBuffer.put(transaction.id, transaction)
71
+ if (transactionBuffer.size() > BUFFER_SIZE) {
72
+ transactionBuffer.removeAt(0)
73
+ }
74
+ }
75
+ }
76
+
77
+ private fun canShowNotifications(): Boolean {
78
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
79
+ notificationManager.areNotificationsEnabled()
80
+ } else {
81
+ true
82
+ }
83
+ }
84
+
85
+ fun show(transaction: HttpTransaction) {
86
+ addToBuffer(transaction)
87
+ if (!BaseChuckerActivity.isInForeground && canShowNotifications()) {
88
+ val builder =
89
+ NotificationCompat.Builder(context, TRANSACTIONS_CHANNEL_ID)
90
+ .setContentIntent(transactionsScreenIntent)
91
+ .setLocalOnly(true)
92
+ .setSmallIcon(R.drawable.chucker_ic_transaction_notification)
93
+ .setColor(ContextCompat.getColor(context, R.color.chucker_color_primary))
94
+ .setContentTitle(context.getString(R.string.chucker_http_notification_title))
95
+ .setAutoCancel(true)
96
+ .addAction(createClearAction())
97
+ val inboxStyle = NotificationCompat.InboxStyle()
98
+ synchronized(transactionBuffer) {
99
+ var count = 0
100
+ for (i in transactionBuffer.size() - 1 downTo 0) {
101
+ val bufferedTransaction = transactionBuffer.valueAt(i)
102
+ if ((bufferedTransaction != null) && count < BUFFER_SIZE) {
103
+ if (count == 0) {
104
+ builder.setContentText(bufferedTransaction.notificationText)
105
+ }
106
+ inboxStyle.addLine(bufferedTransaction.notificationText)
107
+ }
108
+ count++
109
+ }
110
+ builder.setStyle(inboxStyle)
111
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
112
+ builder.setSubText(transactionIdsSet.size.toString())
113
+ } else {
114
+ builder.setNumber(transactionIdsSet.size)
115
+ }
116
+ }
117
+ notificationManager.notify(TRANSACTION_NOTIFICATION_ID, builder.build())
118
+ }
119
+ }
120
+
121
+ private fun createClearAction(): NotificationCompat.Action {
122
+ val clearTitle = context.getString(R.string.chucker_clear)
123
+ val clearTransactionsBroadcastIntent =
124
+ Intent(context, ClearDatabaseJobIntentServiceReceiver::class.java)
125
+ val pendingBroadcastIntent =
126
+ PendingIntent.getBroadcast(
127
+ context,
128
+ INTENT_REQUEST_CODE,
129
+ clearTransactionsBroadcastIntent,
130
+ PendingIntent.FLAG_ONE_SHOT or immutableFlag(),
131
+ )
132
+ return NotificationCompat.Action(
133
+ R.drawable.chucker_ic_delete_white,
134
+ clearTitle,
135
+ pendingBroadcastIntent,
136
+ )
137
+ }
138
+
139
+ fun dismissNotifications() {
140
+ notificationManager.cancel(TRANSACTION_NOTIFICATION_ID)
141
+ }
142
+
143
+ private fun immutableFlag() =
144
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
145
+ PendingIntent.FLAG_IMMUTABLE
146
+ } else {
147
+ 0
148
+ }
149
+ }
@@ -0,0 +1,86 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import okhttp3.Headers
4
+ import okhttp3.Response
5
+ import okio.Source
6
+ import okio.buffer
7
+ import okio.gzip
8
+ import okio.source
9
+ import org.brotli.dec.BrotliInputStream
10
+ import java.net.HttpURLConnection.HTTP_NOT_MODIFIED
11
+ import java.net.HttpURLConnection.HTTP_NO_CONTENT
12
+ import java.net.HttpURLConnection.HTTP_OK
13
+ import java.util.Locale
14
+
15
+ private const val HTTP_CONTINUE = 100
16
+
17
+ /** Returns true if the response must have a (possibly 0-length) body. See RFC 7231. */
18
+ internal fun Response.hasBody(): Boolean {
19
+ // HEAD requests never yield a body regardless of the response headers.
20
+ if (request.method == "HEAD") {
21
+ return false
22
+ }
23
+
24
+ val responseCode = code
25
+ if ((responseCode < HTTP_CONTINUE || responseCode >= HTTP_OK) &&
26
+ (responseCode != HTTP_NO_CONTENT) &&
27
+ (responseCode != HTTP_NOT_MODIFIED)
28
+ ) {
29
+ return true
30
+ }
31
+
32
+ // If the Content-Length or Transfer-Encoding headers disagree with the response code, the
33
+ // response is malformed. For best compatibility, we honor the headers.
34
+ return ((contentLength > 0) || isChunked)
35
+ }
36
+
37
+ private val Response.contentLength: Long
38
+ get() {
39
+ return this.header("Content-Length")?.toLongOrNull() ?: -1L
40
+ }
41
+
42
+ internal val Response.isChunked: Boolean
43
+ get() {
44
+ return this.header("Transfer-Encoding").equals("chunked", ignoreCase = true)
45
+ }
46
+
47
+ internal val Response.contentType: String?
48
+ get() {
49
+ return this.header("Content-Type")
50
+ }
51
+
52
+ private val Headers.containsGzip: Boolean
53
+ get() {
54
+ return this["Content-Encoding"].equals("gzip", ignoreCase = true)
55
+ }
56
+
57
+ private val Headers.containsBrotli: Boolean
58
+ get() {
59
+ return this["Content-Encoding"].equals("br", ignoreCase = true)
60
+ }
61
+
62
+ private val supportedEncodings = listOf("identity", "gzip", "br")
63
+
64
+ internal val Headers.hasSupportedContentEncoding: Boolean
65
+ get() =
66
+ get("Content-Encoding")
67
+ ?.takeIf { it.isNotEmpty() }
68
+ ?.let { it.lowercase(Locale.ROOT) in supportedEncodings }
69
+ ?: true
70
+
71
+ internal fun Source.uncompress(headers: Headers) =
72
+ when {
73
+ headers.containsGzip -> gzip()
74
+ headers.containsBrotli -> BrotliInputStream(this.buffer().inputStream()).source()
75
+ else -> this
76
+ }
77
+
78
+ internal fun Headers.redact(names: Iterable<String>): Headers {
79
+ val builder = newBuilder()
80
+ for (name in names()) {
81
+ if (names.any { userHeader -> userHeader.equals(name, ignoreCase = true) }) {
82
+ builder[name] = "**"
83
+ }
84
+ }
85
+ return builder.build()
86
+ }
@@ -0,0 +1,34 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import okio.Buffer
4
+ import okio.ByteString
5
+ import java.io.EOFException
6
+ import kotlin.math.min
7
+
8
+ private const val MAX_PREFIX_SIZE = 64L
9
+ private const val CODE_POINT_SIZE = 16
10
+
11
+ /**
12
+ * Returns true if the [Buffer] contains human readable text. Uses a small sample
13
+ * of code points to detect unicode control characters commonly used in binary file signatures.
14
+ */
15
+ internal val Buffer.isProbablyPlainText
16
+ get() =
17
+ try {
18
+ val prefix = Buffer()
19
+ val byteCount = min(size, MAX_PREFIX_SIZE)
20
+ copyTo(prefix, 0, byteCount)
21
+ sequence { while (!prefix.exhausted()) yield(prefix.readUtf8CodePoint()) }
22
+ .take(CODE_POINT_SIZE)
23
+ .all { codePoint -> codePoint.isPlainTextChar() }
24
+ } catch (_: EOFException) {
25
+ false // Truncated UTF-8 sequence
26
+ }
27
+
28
+ internal val ByteString.isProbablyPlainText: Boolean
29
+ get() {
30
+ val byteCount = min(size, MAX_PREFIX_SIZE.toInt())
31
+ return Buffer().write(this, offset = 0, byteCount).isProbablyPlainText
32
+ }
33
+
34
+ private fun Int.isPlainTextChar() = Character.isWhitespace(this) || !Character.isISOControl(this)
@@ -0,0 +1,30 @@
1
+ package com.securitysuite.internal.support
2
+
3
+ import com.securitysuite.api.BodyDecoder
4
+ import okhttp3.Headers
5
+ import okhttp3.MediaType
6
+ import okhttp3.Request
7
+ import okhttp3.Response
8
+ import okio.ByteString
9
+ import kotlin.text.Charsets.UTF_8
10
+
11
+ internal object PlainTextDecoder : BodyDecoder {
12
+ override fun decodeRequest(
13
+ request: Request,
14
+ body: ByteString,
15
+ ) = body.tryDecodeAsPlainText(request.headers, request.body?.contentType())
16
+
17
+ override fun decodeResponse(
18
+ response: Response,
19
+ body: ByteString,
20
+ ) = body.tryDecodeAsPlainText(response.headers, response.body?.contentType())
21
+
22
+ private fun ByteString.tryDecodeAsPlainText(
23
+ headers: Headers,
24
+ contentType: MediaType?,
25
+ ) = if (headers.hasSupportedContentEncoding && isProbablyPlainText) {
26
+ string(contentType?.charset() ?: UTF_8)
27
+ } else {
28
+ null
29
+ }
30
+ }