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.
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/android/build.gradle +46 -28
- package/android/src/main/AndroidManifest.xml +47 -0
- package/android/src/main/{AndroidManifestDeprecated.xml → AndroidManifestNew.xml} +1 -2
- package/android/src/main/java/com/securitysuite/NetworkLogger.java +25 -0
- package/android/src/main/java/com/securitysuite/Sslpinning.java +39 -54
- package/android/src/main/java/com/securitysuite/api/BodyDecoder.kt +35 -0
- package/android/src/main/java/com/securitysuite/api/Chucker.kt +108 -0
- package/android/src/main/java/com/securitysuite/api/ChuckerCollector.kt +129 -0
- package/android/src/main/java/com/securitysuite/api/ChuckerInterceptor.kt +280 -0
- package/android/src/main/java/com/securitysuite/api/ExportFormat.kt +12 -0
- package/android/src/main/java/com/securitysuite/api/RetentionManager.kt +109 -0
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpHeader.kt +8 -0
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransaction.kt +344 -0
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransactionTuple.kt +62 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/Har.kt +13 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/Log.kt +24 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Browser.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Creator.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Entry.kt +49 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Page.kt +14 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Cache.kt +12 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Header.kt +17 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Request.kt +35 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Response.kt +33 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Timings.kt +25 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/cache/SecondaryRequest.kt +13 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/PostData.kt +20 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/QueryString.kt +23 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/postdata/Params.kt +13 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/response/Content.kt +30 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/page/PageTimings.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/data/model/DialogData.kt +8 -0
- package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionDatabaseRepository.kt +60 -0
- package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionRepository.kt +33 -0
- package/android/src/main/java/com/securitysuite/internal/data/repository/RepositoryProvider.kt +38 -0
- package/android/src/main/java/com/securitysuite/internal/data/room/ChuckerDatabase.kt +22 -0
- package/android/src/main/java/com/securitysuite/internal/data/room/HttpTransactionDao.kt +53 -0
- package/android/src/main/java/com/securitysuite/internal/support/BitmapUtils.kt +45 -0
- package/android/src/main/java/com/securitysuite/internal/support/CacheDirectoryProvider.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/support/ChessboardDrawable.kt +76 -0
- package/android/src/main/java/com/securitysuite/internal/support/ChuckerFileProvider.kt +9 -0
- package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseJobIntentServiceReceiver.kt +14 -0
- package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseService.kt +32 -0
- package/android/src/main/java/com/securitysuite/internal/support/ContextExt.kt +22 -0
- package/android/src/main/java/com/securitysuite/internal/support/DepletingSource.kt +37 -0
- package/android/src/main/java/com/securitysuite/internal/support/FileFactory.kt +30 -0
- package/android/src/main/java/com/securitysuite/internal/support/FileSaver.kt +41 -0
- package/android/src/main/java/com/securitysuite/internal/support/FormatUtils.kt +117 -0
- package/android/src/main/java/com/securitysuite/internal/support/FormattedUrl.kt +76 -0
- package/android/src/main/java/com/securitysuite/internal/support/HarUtils.kt +28 -0
- package/android/src/main/java/com/securitysuite/internal/support/HttpUrlUtils.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/support/JsonConverter.kt +19 -0
- package/android/src/main/java/com/securitysuite/internal/support/LimitingSource.kt +22 -0
- package/android/src/main/java/com/securitysuite/internal/support/LiveDataUtils.kt +68 -0
- package/android/src/main/java/com/securitysuite/internal/support/Logger.kt +43 -0
- package/android/src/main/java/com/securitysuite/internal/support/NotificationHelper.kt +149 -0
- package/android/src/main/java/com/securitysuite/internal/support/OkHttpUtils.kt +86 -0
- package/android/src/main/java/com/securitysuite/internal/support/OkioUtils.kt +34 -0
- package/android/src/main/java/com/securitysuite/internal/support/PlainTextDecoder.kt +30 -0
- package/android/src/main/java/com/securitysuite/internal/support/ReportingSink.kt +114 -0
- package/android/src/main/java/com/securitysuite/internal/support/RequestProcessor.kt +102 -0
- package/android/src/main/java/com/securitysuite/internal/support/ResponseProcessor.kt +170 -0
- package/android/src/main/java/com/securitysuite/internal/support/SearchHighlightUtil.kt +80 -0
- package/android/src/main/java/com/securitysuite/internal/support/Sharable.kt +86 -0
- package/android/src/main/java/com/securitysuite/internal/support/SpanTextUtil.kt +202 -0
- package/android/src/main/java/com/securitysuite/internal/support/TeeSource.kt +68 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionCurlCommandSharable.kt +46 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsHarSharable.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsSharable.kt +73 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDiffCallback.kt +26 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionListDetailsSharable.kt +23 -0
- package/android/src/main/java/com/securitysuite/internal/ui/BaseChuckerActivity.kt +35 -0
- package/android/src/main/java/com/securitysuite/internal/ui/MainActivity.kt +375 -0
- package/android/src/main/java/com/securitysuite/internal/ui/MainViewModel.kt +47 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/PayloadType.kt +6 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/ProtocolResources.kt +14 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionActivity.kt +186 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionAdapter.kt +139 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionOverviewFragment.kt +100 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPagerAdapter.kt +29 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadAdapter.kt +269 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadFragment.kt +529 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionViewModel.kt +69 -0
- package/android/src/main/res/A.java +12 -0
- package/android/src/main/res/color/chucker_fab_background_colour.xml +5 -0
- package/android/src/main/res/drawable/chucker_empty_payload.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_arrow_down.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_copy.xml +12 -0
- package/android/src/main/res/drawable/chucker_ic_decoded_url_white.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_delete_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_encoded_url_white.xml +11 -0
- package/android/src/main/res/drawable/chucker_ic_graphql.xml +27 -0
- package/android/src/main/res/drawable/chucker_ic_http.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_https.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_launcher_foreground.xml +14 -0
- package/android/src/main/res/drawable/chucker_ic_save_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_search_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_share_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_transaction_notification.xml +9 -0
- package/android/src/main/res/layout/activity_main.xml +83 -0
- package/android/src/main/res/layout/activity_transaction.xml +48 -0
- package/android/src/main/res/layout/fragment_transaction_overview.xml +365 -0
- package/android/src/main/res/layout/fragment_transaction_payload.xml +132 -0
- package/android/src/main/res/layout/list_item_transaction.xml +122 -0
- package/android/src/main/res/layout/transaction_item_body_line.xml +13 -0
- package/android/src/main/res/layout/transaction_item_copy.xml +19 -0
- package/android/src/main/res/layout/transaction_item_headers.xml +12 -0
- package/android/src/main/res/layout/transaction_item_image.xml +16 -0
- package/android/src/main/res/menu/chucker_transaction.xml +46 -0
- package/android/src/main/res/menu/chucker_transactions_list.xml +41 -0
- package/android/src/main/res/mipmap-anydpi-v26/chucker_ic_launcher.xml +5 -0
- package/android/src/main/res/mipmap-hdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xhdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxhdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxxhdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/values/chucker_ic_launcher_background.xml +4 -0
- package/android/src/main/res/values/colors.xml +38 -0
- package/android/src/main/res/values/dimens.xml +10 -0
- package/android/src/main/res/values/public.xml +5 -0
- package/android/src/main/res/values/strings.xml +77 -0
- package/android/src/main/res/values/styles.xml +44 -0
- package/android/src/main/res/values-es/strings.xml +75 -0
- package/android/src/main/res/values-night/colors.xml +32 -0
- package/android/src/main/res/xml/chucker_provider_paths.xml +4 -0
- package/ios/SecuritySuite.swift +0 -2
- package/ios/SslPinning.swift +0 -26
- package/lib/commonjs/helpers.js +1 -1
- package/lib/commonjs/helpers.js.map +1 -1
- package/lib/commonjs/index.js +23 -40
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/helpers.js +3 -1
- package/lib/module/helpers.js.map +1 -1
- package/lib/module/index.js +21 -33
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -0
- package/lib/typescript/{index.d.ts → commonjs/src/index.d.ts} +1 -6
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/helpers.d.ts +3 -0
- package/lib/typescript/module/src/helpers.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +72 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/package.json +70 -43
- package/react-native-security-suite.podspec +23 -15
- package/src/helpers.ts +1 -1
- package/src/index.tsx +5 -18
- package/android/src/main/java/com/securitysuite/AndroidLogger.kt +0 -19
- package/android/src/main/java/com/securitysuite/modifier/Base64Decoder.kt +0 -11
- package/android/src/main/java/com/securitysuite/modifier/BasicAuthorizationHeaderModifier.kt +0 -16
- package/ios/SecuritySuite.xcodeproj/project.pbxproj +0 -293
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/m.navabifar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/SecuritySuite.xcodeproj/xcuserdata/m.navabifar.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/lib/typescript/helpers.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- /package/lib/typescript/{helpers.d.ts → commonjs/src/helpers.d.ts} +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.text.SpannableStringBuilder
|
|
5
|
+
import android.text.Spanned
|
|
6
|
+
import android.text.style.ForegroundColorSpan
|
|
7
|
+
import androidx.core.content.ContextCompat
|
|
8
|
+
import com.securitysuite.R
|
|
9
|
+
|
|
10
|
+
public class SpanTextUtil(context: Context) {
|
|
11
|
+
private val jsonKeyColor: Int
|
|
12
|
+
private val jsonValueColor: Int
|
|
13
|
+
private val jsonDigitsAndNullValueColor: Int
|
|
14
|
+
private val jsonSignElementsColor: Int
|
|
15
|
+
private val jsonBooleanColor: Int
|
|
16
|
+
|
|
17
|
+
private companion object {
|
|
18
|
+
// corresponds to length of word 'true'
|
|
19
|
+
private const val BOOLEAN_TRUE_INDEX_OFFSET = 4
|
|
20
|
+
|
|
21
|
+
// corresponds to length of word 'false'
|
|
22
|
+
private const val BOOLEAN_FALSE_INDEX_OFFSET = 5
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
init {
|
|
26
|
+
jsonKeyColor = ContextCompat.getColor(context, R.color.chucker_json_key_color)
|
|
27
|
+
jsonValueColor = ContextCompat.getColor(context, R.color.chucker_json_value_color)
|
|
28
|
+
jsonDigitsAndNullValueColor =
|
|
29
|
+
ContextCompat.getColor(context, R.color.chucker_json_digit_and_null_value_color)
|
|
30
|
+
jsonSignElementsColor = ContextCompat.getColor(context, R.color.chucker_json_elements_color)
|
|
31
|
+
jsonBooleanColor = ContextCompat.getColor(context, R.color.chucker_json_boolean_color)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private enum class TokenType(val delimiters: Set<String>) {
|
|
35
|
+
STRING(setOf("\"")),
|
|
36
|
+
ARRAY(setOf("[", "]")),
|
|
37
|
+
OBJECT(setOf("{", "}")),
|
|
38
|
+
KEY_SEPARATOR(setOf(":")),
|
|
39
|
+
VALUE_SEPARATOR(setOf(",")),
|
|
40
|
+
BOOLEAN(setOf("true", "false")),
|
|
41
|
+
NONE(setOf()),
|
|
42
|
+
;
|
|
43
|
+
|
|
44
|
+
companion object {
|
|
45
|
+
val allPossibleTokens = values().map { it.delimiters }.flatten().toSet()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private fun CharSequence.indexOfNextToken(startIndex: Int = 0): Pair<Int, TokenType> {
|
|
50
|
+
val (index, matched) =
|
|
51
|
+
findAnyOf(
|
|
52
|
+
strings = TokenType.allPossibleTokens,
|
|
53
|
+
startIndex = startIndex,
|
|
54
|
+
ignoreCase = true,
|
|
55
|
+
) ?: return -1 to TokenType.NONE
|
|
56
|
+
val tokenType =
|
|
57
|
+
when (matched) {
|
|
58
|
+
in TokenType.ARRAY.delimiters -> TokenType.ARRAY
|
|
59
|
+
in TokenType.OBJECT.delimiters -> TokenType.OBJECT
|
|
60
|
+
in TokenType.KEY_SEPARATOR.delimiters -> TokenType.KEY_SEPARATOR
|
|
61
|
+
in TokenType.VALUE_SEPARATOR.delimiters -> TokenType.VALUE_SEPARATOR
|
|
62
|
+
in TokenType.STRING.delimiters -> TokenType.STRING
|
|
63
|
+
in TokenType.BOOLEAN.delimiters -> TokenType.BOOLEAN
|
|
64
|
+
else -> null
|
|
65
|
+
}
|
|
66
|
+
tokenType?.let {
|
|
67
|
+
return index to it
|
|
68
|
+
}
|
|
69
|
+
return -1 to TokenType.NONE
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun CharSequence.indexOfNextUnescapedQuote(startIndex: Int = 0): Int {
|
|
73
|
+
var index = indexOf('"', startIndex)
|
|
74
|
+
while (index < length) {
|
|
75
|
+
if (this[index] == '"' && (index == 0 || this[index - 1] != '\\')) {
|
|
76
|
+
return index
|
|
77
|
+
}
|
|
78
|
+
index = indexOf('"', index + 1)
|
|
79
|
+
}
|
|
80
|
+
return -1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public fun spanJson(input: CharSequence): SpannableStringBuilder {
|
|
84
|
+
// First handle the pretty printing step via gson built-in support
|
|
85
|
+
val prettyPrintedInput = FormatUtils.formatJson(input.toString())
|
|
86
|
+
|
|
87
|
+
var lastTokenType: TokenType? = null
|
|
88
|
+
var index = 0
|
|
89
|
+
|
|
90
|
+
val sb = SpannableStringBuilder(prettyPrintedInput)
|
|
91
|
+
// First we set a span for all text to match the digits and null value color since other
|
|
92
|
+
// cases will be overridden below
|
|
93
|
+
sb.setColor(0, prettyPrintedInput.length, jsonDigitsAndNullValueColor)
|
|
94
|
+
while (index < prettyPrintedInput.length) {
|
|
95
|
+
val (tokenIndex, tokenType) = prettyPrintedInput.indexOfNextToken(startIndex = index)
|
|
96
|
+
when (tokenType) {
|
|
97
|
+
TokenType.BOOLEAN ->
|
|
98
|
+
sb.setBooleanColor(tokenIndex).also { endIndex ->
|
|
99
|
+
index = endIndex
|
|
100
|
+
}
|
|
101
|
+
TokenType.ARRAY,
|
|
102
|
+
TokenType.OBJECT,
|
|
103
|
+
TokenType.KEY_SEPARATOR,
|
|
104
|
+
TokenType.VALUE_SEPARATOR,
|
|
105
|
+
-> {
|
|
106
|
+
sb.setColor(
|
|
107
|
+
start = tokenIndex,
|
|
108
|
+
end = tokenIndex + 1,
|
|
109
|
+
color = jsonSignElementsColor,
|
|
110
|
+
)
|
|
111
|
+
index = tokenIndex + 1
|
|
112
|
+
}
|
|
113
|
+
TokenType.STRING ->
|
|
114
|
+
sb.setStringColor(tokenIndex, lastTokenType)?.also { endIndex ->
|
|
115
|
+
index = endIndex + 1
|
|
116
|
+
} ?: return sb
|
|
117
|
+
TokenType.NONE -> return sb
|
|
118
|
+
}
|
|
119
|
+
lastTokenType = tokenType
|
|
120
|
+
}
|
|
121
|
+
return sb
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private fun SpannableStringBuilder.setColor(
|
|
125
|
+
start: Int,
|
|
126
|
+
end: Int,
|
|
127
|
+
color: Int,
|
|
128
|
+
): SpannableStringBuilder {
|
|
129
|
+
this.setSpan(
|
|
130
|
+
ChuckerForegroundColorSpan(color),
|
|
131
|
+
start,
|
|
132
|
+
end,
|
|
133
|
+
Spanned.SPAN_INCLUSIVE_INCLUSIVE,
|
|
134
|
+
)
|
|
135
|
+
return this
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Given the tokenIndex, attempt to format the boolean value by first checking to see if the
|
|
140
|
+
* token starts with a 't' for true or not.
|
|
141
|
+
*
|
|
142
|
+
* Returns the index of the end of the spanned boolean value
|
|
143
|
+
*/
|
|
144
|
+
private fun SpannableStringBuilder.setBooleanColor(tokenIndex: Int): Int {
|
|
145
|
+
val endIndex =
|
|
146
|
+
if (this[tokenIndex].equals('t', ignoreCase = true)) {
|
|
147
|
+
tokenIndex + BOOLEAN_TRUE_INDEX_OFFSET
|
|
148
|
+
} else {
|
|
149
|
+
tokenIndex + BOOLEAN_FALSE_INDEX_OFFSET
|
|
150
|
+
}
|
|
151
|
+
setColor(
|
|
152
|
+
start = tokenIndex,
|
|
153
|
+
end = endIndex,
|
|
154
|
+
color = jsonBooleanColor,
|
|
155
|
+
)
|
|
156
|
+
return endIndex
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Given the tokenIndex and lastTokenType, attempt to format the color string by searching for
|
|
161
|
+
* the next unescaped quote mark in the string. If none is found, we return null immediately
|
|
162
|
+
* which should signal that the JSON string is incomplete and all further formatting should stop.
|
|
163
|
+
*
|
|
164
|
+
* Otherwise, we will return the index of the end of the spanned string
|
|
165
|
+
*/
|
|
166
|
+
private fun SpannableStringBuilder.setStringColor(
|
|
167
|
+
tokenIndex: Int,
|
|
168
|
+
lastTokenType: TokenType? = null,
|
|
169
|
+
): Int? {
|
|
170
|
+
val color =
|
|
171
|
+
when (lastTokenType) {
|
|
172
|
+
TokenType.ARRAY,
|
|
173
|
+
TokenType.OBJECT,
|
|
174
|
+
TokenType.VALUE_SEPARATOR,
|
|
175
|
+
TokenType.NONE,
|
|
176
|
+
null,
|
|
177
|
+
-> {
|
|
178
|
+
jsonKeyColor
|
|
179
|
+
}
|
|
180
|
+
else -> {
|
|
181
|
+
jsonValueColor
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@Suppress("TooGenericExceptionCaught", "SwallowedException")
|
|
186
|
+
val endIndex =
|
|
187
|
+
try {
|
|
188
|
+
this.indexOfNextUnescapedQuote(tokenIndex + 1)
|
|
189
|
+
} catch (e: Exception) {
|
|
190
|
+
-1
|
|
191
|
+
}
|
|
192
|
+
// if we somehow get an incomplete string, we lose the ability to parse any other
|
|
193
|
+
// tokens, so just return now
|
|
194
|
+
if (endIndex < tokenIndex) {
|
|
195
|
+
return null
|
|
196
|
+
}
|
|
197
|
+
setColor(start = tokenIndex, end = endIndex + 1, color)
|
|
198
|
+
return endIndex
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public class ChuckerForegroundColorSpan(color: Int) : ForegroundColorSpan(color)
|
|
202
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import okio.Buffer
|
|
4
|
+
import okio.Sink
|
|
5
|
+
import okio.Source
|
|
6
|
+
import okio.Timeout
|
|
7
|
+
import java.io.IOException
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A source that acts as a tee operator - https://en.wikipedia.org/wiki/Tee_(command).
|
|
11
|
+
*
|
|
12
|
+
* It takes the input [upstream] and reads from it serving the bytes to the end consumer
|
|
13
|
+
* like a regular [Source]. While bytes are read from the [upstream] the are also copied
|
|
14
|
+
* to a [sideStream].
|
|
15
|
+
*/
|
|
16
|
+
internal class TeeSource(
|
|
17
|
+
private val upstream: Source,
|
|
18
|
+
private val sideStream: Sink,
|
|
19
|
+
) : Source {
|
|
20
|
+
private val tempBuffer = Buffer()
|
|
21
|
+
private var isFailure = false
|
|
22
|
+
|
|
23
|
+
override fun read(
|
|
24
|
+
sink: Buffer,
|
|
25
|
+
byteCount: Long,
|
|
26
|
+
): Long {
|
|
27
|
+
val bytesRead = upstream.read(sink, byteCount)
|
|
28
|
+
|
|
29
|
+
if (bytesRead == -1L) {
|
|
30
|
+
safeCloseSideStream()
|
|
31
|
+
return -1L
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isFailure) {
|
|
35
|
+
copyBytesToSideStream(sink, bytesRead)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return bytesRead
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private fun copyBytesToSideStream(
|
|
42
|
+
sink: Buffer,
|
|
43
|
+
bytesRead: Long,
|
|
44
|
+
) {
|
|
45
|
+
val offset = sink.size - bytesRead
|
|
46
|
+
sink.copyTo(tempBuffer, offset, bytesRead)
|
|
47
|
+
try {
|
|
48
|
+
sideStream.write(tempBuffer, bytesRead)
|
|
49
|
+
} catch (_: IOException) {
|
|
50
|
+
isFailure = true
|
|
51
|
+
safeCloseSideStream()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun close() {
|
|
56
|
+
safeCloseSideStream()
|
|
57
|
+
upstream.close()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun safeCloseSideStream() =
|
|
61
|
+
try {
|
|
62
|
+
sideStream.close()
|
|
63
|
+
} catch (_: IOException) {
|
|
64
|
+
isFailure = true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun timeout(): Timeout = upstream.timeout()
|
|
68
|
+
}
|
package/android/src/main/java/com/securitysuite/internal/support/TransactionCurlCommandSharable.kt
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.securitysuite.internal.data.entity.HttpHeader
|
|
5
|
+
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
6
|
+
import okio.Buffer
|
|
7
|
+
import okio.Source
|
|
8
|
+
|
|
9
|
+
internal class TransactionCurlCommandSharable(
|
|
10
|
+
private val transaction: HttpTransaction,
|
|
11
|
+
) : Sharable {
|
|
12
|
+
override fun toSharableContent(context: Context): Source =
|
|
13
|
+
Buffer().apply {
|
|
14
|
+
var compressed = false
|
|
15
|
+
writeUtf8("curl -X ${transaction.method}")
|
|
16
|
+
val headers = transaction.getParsedRequestHeaders()
|
|
17
|
+
|
|
18
|
+
headers?.forEach { header ->
|
|
19
|
+
if (isCompressed(header)) {
|
|
20
|
+
compressed = true
|
|
21
|
+
}
|
|
22
|
+
val headerValue = escapeHeaderValue(header.value)
|
|
23
|
+
writeUtf8(" -H \"${header.name}: ${headerValue}\"")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
val requestBody = transaction.requestBody
|
|
27
|
+
if (!requestBody.isNullOrEmpty()) {
|
|
28
|
+
// try to keep to a single line and use a subshell to preserve any line breaks
|
|
29
|
+
writeUtf8(" --data $'${requestBody.replace("\n", "\\n")}'")
|
|
30
|
+
}
|
|
31
|
+
writeUtf8((if (compressed) " --compressed " else " ") + transaction.getFormattedUrl(encode = false))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private fun isCompressed(header: HttpHeader): Boolean {
|
|
35
|
+
return (
|
|
36
|
+
"Accept-Encoding".equals(header.name, ignoreCase = true) &&
|
|
37
|
+
"gzip".contains(header.value, ignoreCase = true) ||
|
|
38
|
+
"br".contains(header.value, ignoreCase = true)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private fun escapeHeaderValue(value: String): String {
|
|
43
|
+
// escape double quotes from header value to prevent getting an invalid curl
|
|
44
|
+
return value.replace("\"", "\\\"")
|
|
45
|
+
}
|
|
46
|
+
}
|
package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsHarSharable.kt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import okio.Buffer
|
|
5
|
+
import okio.Source
|
|
6
|
+
|
|
7
|
+
internal class TransactionDetailsHarSharable(
|
|
8
|
+
private val content: String,
|
|
9
|
+
) : Sharable {
|
|
10
|
+
override fun toSharableContent(context: Context): Source = Buffer().writeUtf8("$content\n")
|
|
11
|
+
}
|
package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsSharable.kt
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.securitysuite.R
|
|
5
|
+
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
6
|
+
import okio.Buffer
|
|
7
|
+
import okio.Source
|
|
8
|
+
|
|
9
|
+
internal class TransactionDetailsSharable(
|
|
10
|
+
private val transaction: HttpTransaction,
|
|
11
|
+
private val encodeUrls: Boolean,
|
|
12
|
+
) : Sharable {
|
|
13
|
+
override fun toSharableContent(context: Context): Source =
|
|
14
|
+
Buffer().apply {
|
|
15
|
+
writeUtf8("${context.getString(R.string.chucker_url)}: ${transaction.getFormattedUrl(encodeUrls)}\n")
|
|
16
|
+
writeUtf8("${context.getString(R.string.chucker_method)}: ${transaction.method}\n")
|
|
17
|
+
writeUtf8("${context.getString(R.string.chucker_protocol)}: ${transaction.protocol}\n")
|
|
18
|
+
writeUtf8("${context.getString(R.string.chucker_status)}: ${transaction.status}\n")
|
|
19
|
+
writeUtf8("${context.getString(R.string.chucker_response)}: ${transaction.responseSummaryText}\n")
|
|
20
|
+
val isSsl = if (transaction.isSsl) R.string.chucker_yes else R.string.chucker_no
|
|
21
|
+
writeUtf8("${context.getString(R.string.chucker_ssl)}: ${context.getString(isSsl)}\n")
|
|
22
|
+
writeUtf8("\n")
|
|
23
|
+
writeUtf8("${context.getString(R.string.chucker_request_time)}: ${transaction.requestDateString}\n")
|
|
24
|
+
writeUtf8("${context.getString(R.string.chucker_response_time)}: ${transaction.responseDateString}\n")
|
|
25
|
+
writeUtf8("${context.getString(R.string.chucker_duration)}: ${transaction.durationString}\n")
|
|
26
|
+
writeUtf8("\n")
|
|
27
|
+
writeUtf8("${context.getString(R.string.chucker_request_size)}: ${transaction.requestSizeString}\n")
|
|
28
|
+
writeUtf8("${context.getString(R.string.chucker_response_size)}: ${transaction.responseSizeString}\n")
|
|
29
|
+
writeUtf8("${context.getString(R.string.chucker_total_size)}: ${transaction.totalSizeString}\n")
|
|
30
|
+
writeUtf8("\n")
|
|
31
|
+
writeUtf8("---------- ${context.getString(R.string.chucker_request)} ----------\n\n")
|
|
32
|
+
var headers = FormatUtils.formatHeaders(transaction.getParsedRequestHeaders(), false)
|
|
33
|
+
if (headers.isNotBlank()) {
|
|
34
|
+
writeUtf8(headers)
|
|
35
|
+
writeUtf8("\n")
|
|
36
|
+
}
|
|
37
|
+
writeUtf8(
|
|
38
|
+
if (transaction.requestBody.isNullOrBlank()) {
|
|
39
|
+
val resId =
|
|
40
|
+
if (transaction.isResponseBodyEncoded) {
|
|
41
|
+
R.string.chucker_body_omitted
|
|
42
|
+
} else {
|
|
43
|
+
R.string.chucker_body_empty
|
|
44
|
+
}
|
|
45
|
+
context.getString(resId)
|
|
46
|
+
} else {
|
|
47
|
+
transaction.getFormattedRequestBody()
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
writeUtf8("\n\n")
|
|
51
|
+
writeUtf8("---------- ${context.getString(R.string.chucker_response)} ----------\n\n")
|
|
52
|
+
headers = FormatUtils.formatHeaders(transaction.getParsedResponseHeaders(), false)
|
|
53
|
+
|
|
54
|
+
if (headers.isNotBlank()) {
|
|
55
|
+
writeUtf8(headers)
|
|
56
|
+
writeUtf8("\n")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
writeUtf8(
|
|
60
|
+
if (transaction.responseBody.isNullOrBlank()) {
|
|
61
|
+
val resId =
|
|
62
|
+
if (transaction.isResponseBodyEncoded) {
|
|
63
|
+
R.string.chucker_body_omitted
|
|
64
|
+
} else {
|
|
65
|
+
R.string.chucker_body_empty
|
|
66
|
+
}
|
|
67
|
+
context.getString(resId)
|
|
68
|
+
} else {
|
|
69
|
+
transaction.getFormattedResponseBody()
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import androidx.recyclerview.widget.DiffUtil
|
|
4
|
+
import com.securitysuite.internal.data.entity.HttpTransactionTuple
|
|
5
|
+
|
|
6
|
+
internal object TransactionDiffCallback : DiffUtil.ItemCallback<HttpTransactionTuple>() {
|
|
7
|
+
override fun areItemsTheSame(
|
|
8
|
+
oldItem: HttpTransactionTuple,
|
|
9
|
+
newItem: HttpTransactionTuple,
|
|
10
|
+
): Boolean {
|
|
11
|
+
return oldItem.id == newItem.id
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun areContentsTheSame(
|
|
15
|
+
oldItem: HttpTransactionTuple,
|
|
16
|
+
newItem: HttpTransactionTuple,
|
|
17
|
+
): Boolean {
|
|
18
|
+
return oldItem == newItem
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Overriding function is empty on purpose to avoid flickering by default animator
|
|
22
|
+
override fun getChangePayload(
|
|
23
|
+
oldItem: HttpTransactionTuple,
|
|
24
|
+
newItem: HttpTransactionTuple,
|
|
25
|
+
) = Unit
|
|
26
|
+
}
|
package/android/src/main/java/com/securitysuite/internal/support/TransactionListDetailsSharable.kt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package com.securitysuite.internal.support
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.securitysuite.R.string
|
|
5
|
+
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
6
|
+
import okio.Buffer
|
|
7
|
+
import okio.Source
|
|
8
|
+
|
|
9
|
+
internal class TransactionListDetailsSharable(
|
|
10
|
+
transactions: List<HttpTransaction>,
|
|
11
|
+
encodeUrls: Boolean,
|
|
12
|
+
) : Sharable {
|
|
13
|
+
private val transactions = transactions.map { TransactionDetailsSharable(it, encodeUrls) }
|
|
14
|
+
|
|
15
|
+
override fun toSharableContent(context: Context): Source =
|
|
16
|
+
Buffer().writeUtf8(
|
|
17
|
+
transactions.joinToString(
|
|
18
|
+
separator = "\n${context.getString(string.chucker_export_separator)}\n",
|
|
19
|
+
prefix = "${context.getString(string.chucker_export_prefix)}\n",
|
|
20
|
+
postfix = "\n${context.getString(string.chucker_export_postfix)}\n",
|
|
21
|
+
) { it.toSharableUtf8Content(context) },
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.securitysuite.internal.ui
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import android.widget.Toast
|
|
5
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
6
|
+
import com.securitysuite.internal.data.repository.RepositoryProvider
|
|
7
|
+
|
|
8
|
+
internal abstract class BaseChuckerActivity : AppCompatActivity() {
|
|
9
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
10
|
+
super.onCreate(savedInstanceState)
|
|
11
|
+
RepositoryProvider.initialize(applicationContext)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun onResume() {
|
|
15
|
+
super.onResume()
|
|
16
|
+
isInForeground = true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun onPause() {
|
|
20
|
+
super.onPause()
|
|
21
|
+
isInForeground = false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
companion object {
|
|
25
|
+
var isInForeground: Boolean = false
|
|
26
|
+
private set
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fun showToast(
|
|
30
|
+
message: String,
|
|
31
|
+
toastDuration: Int = Toast.LENGTH_SHORT,
|
|
32
|
+
) {
|
|
33
|
+
Toast.makeText(this.applicationContext, message, toastDuration).show()
|
|
34
|
+
}
|
|
35
|
+
}
|