react-native-security-suite 0.6.7 → 0.7.0
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/android/build.gradle +0 -15
- package/android/src/main/AndroidManifest.xml +3 -48
- package/package.json +1 -1
- package/android/src/main/java/com/securitysuite/NetworkLogger.java +0 -25
- package/android/src/main/java/com/securitysuite/api/BodyDecoder.kt +0 -35
- package/android/src/main/java/com/securitysuite/api/Chucker.kt +0 -108
- package/android/src/main/java/com/securitysuite/api/ChuckerCollector.kt +0 -129
- package/android/src/main/java/com/securitysuite/api/ChuckerInterceptor.kt +0 -280
- package/android/src/main/java/com/securitysuite/api/ExportFormat.kt +0 -12
- package/android/src/main/java/com/securitysuite/api/RetentionManager.kt +0 -109
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpHeader.kt +0 -8
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransaction.kt +0 -344
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransactionTuple.kt +0 -62
- package/android/src/main/java/com/securitysuite/internal/data/har/Har.kt +0 -13
- package/android/src/main/java/com/securitysuite/internal/data/har/Log.kt +0 -24
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Browser.kt +0 -11
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Creator.kt +0 -11
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Entry.kt +0 -49
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Page.kt +0 -14
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Cache.kt +0 -12
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Header.kt +0 -17
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Request.kt +0 -35
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Response.kt +0 -33
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Timings.kt +0 -25
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/cache/SecondaryRequest.kt +0 -13
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/PostData.kt +0 -20
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/QueryString.kt +0 -23
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/postdata/Params.kt +0 -13
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/response/Content.kt +0 -30
- package/android/src/main/java/com/securitysuite/internal/data/har/log/page/PageTimings.kt +0 -11
- package/android/src/main/java/com/securitysuite/internal/data/model/DialogData.kt +0 -8
- package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionDatabaseRepository.kt +0 -60
- package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionRepository.kt +0 -33
- package/android/src/main/java/com/securitysuite/internal/data/repository/RepositoryProvider.kt +0 -38
- package/android/src/main/java/com/securitysuite/internal/data/room/ChuckerDatabase.kt +0 -22
- package/android/src/main/java/com/securitysuite/internal/data/room/HttpTransactionDao.kt +0 -53
- package/android/src/main/java/com/securitysuite/internal/support/BitmapUtils.kt +0 -45
- package/android/src/main/java/com/securitysuite/internal/support/CacheDirectoryProvider.kt +0 -11
- package/android/src/main/java/com/securitysuite/internal/support/ChessboardDrawable.kt +0 -76
- package/android/src/main/java/com/securitysuite/internal/support/ChuckerFileProvider.kt +0 -9
- package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseJobIntentServiceReceiver.kt +0 -14
- package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseService.kt +0 -32
- package/android/src/main/java/com/securitysuite/internal/support/ContextExt.kt +0 -22
- package/android/src/main/java/com/securitysuite/internal/support/DepletingSource.kt +0 -37
- package/android/src/main/java/com/securitysuite/internal/support/FileFactory.kt +0 -30
- package/android/src/main/java/com/securitysuite/internal/support/FileSaver.kt +0 -41
- package/android/src/main/java/com/securitysuite/internal/support/FormatUtils.kt +0 -117
- package/android/src/main/java/com/securitysuite/internal/support/FormattedUrl.kt +0 -76
- package/android/src/main/java/com/securitysuite/internal/support/HarUtils.kt +0 -28
- package/android/src/main/java/com/securitysuite/internal/support/HttpUrlUtils.kt +0 -11
- package/android/src/main/java/com/securitysuite/internal/support/JsonConverter.kt +0 -19
- package/android/src/main/java/com/securitysuite/internal/support/LimitingSource.kt +0 -22
- package/android/src/main/java/com/securitysuite/internal/support/LiveDataUtils.kt +0 -68
- package/android/src/main/java/com/securitysuite/internal/support/Logger.kt +0 -43
- package/android/src/main/java/com/securitysuite/internal/support/NotificationHelper.kt +0 -149
- package/android/src/main/java/com/securitysuite/internal/support/OkHttpUtils.kt +0 -86
- package/android/src/main/java/com/securitysuite/internal/support/OkioUtils.kt +0 -34
- package/android/src/main/java/com/securitysuite/internal/support/PlainTextDecoder.kt +0 -30
- package/android/src/main/java/com/securitysuite/internal/support/ReportingSink.kt +0 -114
- package/android/src/main/java/com/securitysuite/internal/support/RequestProcessor.kt +0 -102
- package/android/src/main/java/com/securitysuite/internal/support/ResponseProcessor.kt +0 -170
- package/android/src/main/java/com/securitysuite/internal/support/SearchHighlightUtil.kt +0 -80
- package/android/src/main/java/com/securitysuite/internal/support/Sharable.kt +0 -86
- package/android/src/main/java/com/securitysuite/internal/support/SpanTextUtil.kt +0 -202
- package/android/src/main/java/com/securitysuite/internal/support/TeeSource.kt +0 -68
- package/android/src/main/java/com/securitysuite/internal/support/TransactionCurlCommandSharable.kt +0 -46
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsHarSharable.kt +0 -11
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsSharable.kt +0 -73
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDiffCallback.kt +0 -26
- package/android/src/main/java/com/securitysuite/internal/support/TransactionListDetailsSharable.kt +0 -23
- package/android/src/main/java/com/securitysuite/internal/ui/BaseChuckerActivity.kt +0 -35
- package/android/src/main/java/com/securitysuite/internal/ui/MainActivity.kt +0 -375
- package/android/src/main/java/com/securitysuite/internal/ui/MainViewModel.kt +0 -47
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/PayloadType.kt +0 -6
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/ProtocolResources.kt +0 -14
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionActivity.kt +0 -186
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionAdapter.kt +0 -139
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionOverviewFragment.kt +0 -100
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPagerAdapter.kt +0 -29
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadAdapter.kt +0 -269
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadFragment.kt +0 -529
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionViewModel.kt +0 -69
- package/android/src/main/res/A.java +0 -12
- package/android/src/main/res/color/chucker_fab_background_colour.xml +0 -5
- package/android/src/main/res/drawable/chucker_empty_payload.xml +0 -10
- package/android/src/main/res/drawable/chucker_ic_arrow_down.xml +0 -10
- package/android/src/main/res/drawable/chucker_ic_copy.xml +0 -12
- package/android/src/main/res/drawable/chucker_ic_decoded_url_white.xml +0 -10
- package/android/src/main/res/drawable/chucker_ic_delete_white.xml +0 -9
- package/android/src/main/res/drawable/chucker_ic_encoded_url_white.xml +0 -11
- package/android/src/main/res/drawable/chucker_ic_graphql.xml +0 -27
- package/android/src/main/res/drawable/chucker_ic_http.xml +0 -10
- package/android/src/main/res/drawable/chucker_ic_https.xml +0 -9
- package/android/src/main/res/drawable/chucker_ic_launcher_foreground.xml +0 -14
- package/android/src/main/res/drawable/chucker_ic_save_white.xml +0 -9
- package/android/src/main/res/drawable/chucker_ic_search_white.xml +0 -9
- package/android/src/main/res/drawable/chucker_ic_share_white.xml +0 -9
- package/android/src/main/res/drawable/chucker_ic_transaction_notification.xml +0 -9
- package/android/src/main/res/layout/activity_main.xml +0 -83
- package/android/src/main/res/layout/activity_transaction.xml +0 -48
- package/android/src/main/res/layout/fragment_transaction_overview.xml +0 -365
- package/android/src/main/res/layout/fragment_transaction_payload.xml +0 -132
- package/android/src/main/res/layout/list_item_transaction.xml +0 -122
- package/android/src/main/res/layout/transaction_item_body_line.xml +0 -13
- package/android/src/main/res/layout/transaction_item_copy.xml +0 -19
- package/android/src/main/res/layout/transaction_item_headers.xml +0 -12
- package/android/src/main/res/layout/transaction_item_image.xml +0 -16
- package/android/src/main/res/menu/chucker_transaction.xml +0 -46
- package/android/src/main/res/menu/chucker_transactions_list.xml +0 -41
- package/android/src/main/res/mipmap-anydpi-v26/chucker_ic_launcher.xml +0 -5
- 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 +0 -4
- package/android/src/main/res/values/colors.xml +0 -38
- package/android/src/main/res/values/dimens.xml +0 -10
- package/android/src/main/res/values/public.xml +0 -5
- package/android/src/main/res/values/strings.xml +0 -77
- package/android/src/main/res/values/styles.xml +0 -44
- package/android/src/main/res/values-es/strings.xml +0 -75
- package/android/src/main/res/values-night/colors.xml +0 -32
- package/android/src/main/res/xml/chucker_provider_paths.xml +0 -4
|
@@ -1,86 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
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)
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
package com.securitysuite.internal.support
|
|
2
|
-
|
|
3
|
-
import okio.Buffer
|
|
4
|
-
import okio.Sink
|
|
5
|
-
import okio.Timeout
|
|
6
|
-
import okio.sink
|
|
7
|
-
import java.io.File
|
|
8
|
-
import java.io.IOException
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A sink that reports result of writing to it via [callback].
|
|
12
|
-
*
|
|
13
|
-
* Takes an input [downstreamFile] and writes bytes from a source into this input. Amount of bytes
|
|
14
|
-
* to copy can be limited with [writeByteLimit]. Results are reported back to a client
|
|
15
|
-
* when sink is closed or when an exception occurs while creating a downstream sink or while
|
|
16
|
-
* writing bytes.
|
|
17
|
-
*/
|
|
18
|
-
internal class ReportingSink(
|
|
19
|
-
private val downstreamFile: File?,
|
|
20
|
-
private val callback: Callback,
|
|
21
|
-
private val writeByteLimit: Long = Long.MAX_VALUE,
|
|
22
|
-
) : Sink {
|
|
23
|
-
private var totalByteCount = 0L
|
|
24
|
-
private var isFailure = false
|
|
25
|
-
private var isClosed = false
|
|
26
|
-
private var downstream =
|
|
27
|
-
try {
|
|
28
|
-
downstreamFile?.sink()
|
|
29
|
-
} catch (e: IOException) {
|
|
30
|
-
callDownstreamFailure(IOException("Failed to use file $downstreamFile by Chucker", e))
|
|
31
|
-
null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
override fun write(
|
|
35
|
-
source: Buffer,
|
|
36
|
-
byteCount: Long,
|
|
37
|
-
) {
|
|
38
|
-
val previousTotalByteCount = totalByteCount
|
|
39
|
-
totalByteCount += byteCount
|
|
40
|
-
if (isFailure || previousTotalByteCount >= writeByteLimit) return
|
|
41
|
-
|
|
42
|
-
val bytesToWrite =
|
|
43
|
-
if (previousTotalByteCount + byteCount <= writeByteLimit) {
|
|
44
|
-
byteCount
|
|
45
|
-
} else {
|
|
46
|
-
writeByteLimit - previousTotalByteCount
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (bytesToWrite == 0L) return
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
downstream?.write(source, bytesToWrite)
|
|
53
|
-
} catch (e: IOException) {
|
|
54
|
-
callDownstreamFailure(e)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
override fun flush() {
|
|
59
|
-
if (isFailure) return
|
|
60
|
-
try {
|
|
61
|
-
downstream?.flush()
|
|
62
|
-
} catch (e: IOException) {
|
|
63
|
-
callDownstreamFailure(e)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
override fun close() {
|
|
68
|
-
if (isClosed) return
|
|
69
|
-
isClosed = true
|
|
70
|
-
safeCloseDownstream()
|
|
71
|
-
callback.onClosed(downstreamFile, totalByteCount)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
override fun timeout(): Timeout = downstream?.timeout() ?: Timeout.NONE
|
|
75
|
-
|
|
76
|
-
private fun callDownstreamFailure(exception: IOException) {
|
|
77
|
-
if (!isFailure) {
|
|
78
|
-
isFailure = true
|
|
79
|
-
safeCloseDownstream()
|
|
80
|
-
callback.onFailure(downstreamFile, exception)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private fun safeCloseDownstream() =
|
|
85
|
-
try {
|
|
86
|
-
downstream?.close()
|
|
87
|
-
} catch (e: IOException) {
|
|
88
|
-
callDownstreamFailure(e)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface Callback {
|
|
92
|
-
/**
|
|
93
|
-
* Called when the sink is closed. All written bytes are copied to the [file].
|
|
94
|
-
* This does not mean that the content of the [file] is valid. Only that the client
|
|
95
|
-
* is done with the writing process.
|
|
96
|
-
*
|
|
97
|
-
* [sourceByteCount] is the exact amount of bytes that the were read from upstream even if
|
|
98
|
-
* the [file] is corrupted or does not exist. It is not limited by [writeByteLimit].
|
|
99
|
-
*/
|
|
100
|
-
fun onClosed(
|
|
101
|
-
file: File?,
|
|
102
|
-
sourceByteCount: Long,
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Called when an [exception] was thrown while processing data.
|
|
107
|
-
* Any written bytes are available in a [file].
|
|
108
|
-
*/
|
|
109
|
-
fun onFailure(
|
|
110
|
-
file: File?,
|
|
111
|
-
exception: IOException,
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
package com.securitysuite.internal.support
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import com.securitysuite.R
|
|
5
|
-
import com.securitysuite.api.BodyDecoder
|
|
6
|
-
import com.securitysuite.api.ChuckerCollector
|
|
7
|
-
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
8
|
-
import okhttp3.Request
|
|
9
|
-
import okio.Buffer
|
|
10
|
-
import okio.ByteString
|
|
11
|
-
import okio.IOException
|
|
12
|
-
|
|
13
|
-
internal class RequestProcessor(
|
|
14
|
-
private val context: Context,
|
|
15
|
-
private val collector: ChuckerCollector,
|
|
16
|
-
private val maxContentLength: Long,
|
|
17
|
-
private val headersToRedact: Set<String>,
|
|
18
|
-
private val bodyDecoders: List<BodyDecoder>,
|
|
19
|
-
) {
|
|
20
|
-
fun process(
|
|
21
|
-
request: Request,
|
|
22
|
-
transaction: HttpTransaction,
|
|
23
|
-
) {
|
|
24
|
-
processMetadata(request, transaction)
|
|
25
|
-
processPayload(request, transaction)
|
|
26
|
-
collector.onRequestSent(transaction)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private fun processMetadata(
|
|
30
|
-
request: Request,
|
|
31
|
-
transaction: HttpTransaction,
|
|
32
|
-
) {
|
|
33
|
-
transaction.apply {
|
|
34
|
-
requestHeadersSize = request.headers.byteCount()
|
|
35
|
-
request.headers.redact(headersToRedact).let {
|
|
36
|
-
setRequestHeaders(it)
|
|
37
|
-
setGraphQlOperationName(it)
|
|
38
|
-
}
|
|
39
|
-
populateUrl(request.url)
|
|
40
|
-
graphQlDetected = isGraphQLRequest(this.graphQlOperationName, request)
|
|
41
|
-
|
|
42
|
-
requestDate = System.currentTimeMillis()
|
|
43
|
-
method = request.method
|
|
44
|
-
requestContentType = request.body?.contentType()?.toString()
|
|
45
|
-
requestPayloadSize = request.body?.contentLength()
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private fun processPayload(
|
|
50
|
-
request: Request,
|
|
51
|
-
transaction: HttpTransaction,
|
|
52
|
-
) {
|
|
53
|
-
val body = request.body ?: return
|
|
54
|
-
if (body.isOneShot()) {
|
|
55
|
-
Logger.info("Skipping one shot request body")
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
if (body.isDuplex()) {
|
|
59
|
-
Logger.info("Skipping duplex request body")
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
val requestSource =
|
|
64
|
-
try {
|
|
65
|
-
Buffer().apply { body.writeTo(this) }
|
|
66
|
-
} catch (e: IOException) {
|
|
67
|
-
Logger.error("Failed to read request payload", e)
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
val limitingSource = LimitingSource(requestSource.uncompress(request.headers), maxContentLength)
|
|
71
|
-
|
|
72
|
-
val contentBuffer = Buffer().apply { limitingSource.use { writeAll(it) } }
|
|
73
|
-
|
|
74
|
-
val decodedContent = decodePayload(request, contentBuffer.readByteString())
|
|
75
|
-
transaction.requestBody = decodedContent
|
|
76
|
-
transaction.isRequestBodyEncoded = decodedContent == null
|
|
77
|
-
if (decodedContent != null && limitingSource.isThresholdReached) {
|
|
78
|
-
transaction.requestBody += context.getString(R.string.chucker_body_content_truncated)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private fun decodePayload(
|
|
83
|
-
request: Request,
|
|
84
|
-
body: ByteString,
|
|
85
|
-
) = bodyDecoders.asSequence()
|
|
86
|
-
.mapNotNull { decoder ->
|
|
87
|
-
try {
|
|
88
|
-
Logger.info("Decoding with: $decoder")
|
|
89
|
-
decoder.decodeRequest(request, body)
|
|
90
|
-
} catch (e: IOException) {
|
|
91
|
-
Logger.warn("Decoder $decoder failed to process request payload", e)
|
|
92
|
-
null
|
|
93
|
-
}
|
|
94
|
-
}.firstOrNull()
|
|
95
|
-
|
|
96
|
-
private fun isGraphQLRequest(
|
|
97
|
-
graphQLOperationName: String?,
|
|
98
|
-
request: Request,
|
|
99
|
-
) = graphQLOperationName != null ||
|
|
100
|
-
request.url.pathSegments.contains("graphql") ||
|
|
101
|
-
request.url.host.contains("graphql")
|
|
102
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
package com.securitysuite.internal.support
|
|
2
|
-
|
|
3
|
-
import com.securitysuite.api.BodyDecoder
|
|
4
|
-
import com.securitysuite.api.ChuckerCollector
|
|
5
|
-
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
6
|
-
import okhttp3.Response
|
|
7
|
-
import okhttp3.ResponseBody.Companion.asResponseBody
|
|
8
|
-
import okio.Buffer
|
|
9
|
-
import okio.ByteString
|
|
10
|
-
import okio.IOException
|
|
11
|
-
import okio.Source
|
|
12
|
-
import okio.buffer
|
|
13
|
-
import okio.source
|
|
14
|
-
import java.io.File
|
|
15
|
-
|
|
16
|
-
internal class ResponseProcessor(
|
|
17
|
-
private val collector: ChuckerCollector,
|
|
18
|
-
private val cacheDirectoryProvider: CacheDirectoryProvider,
|
|
19
|
-
private val maxContentLength: Long,
|
|
20
|
-
private val headersToRedact: Set<String>,
|
|
21
|
-
private val alwaysReadResponseBody: Boolean,
|
|
22
|
-
private val bodyDecoders: List<BodyDecoder>,
|
|
23
|
-
) {
|
|
24
|
-
fun process(
|
|
25
|
-
response: Response,
|
|
26
|
-
transaction: HttpTransaction,
|
|
27
|
-
): Response {
|
|
28
|
-
processResponseMetadata(response, transaction)
|
|
29
|
-
return multiCastResponse(response, transaction)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
private fun processResponseMetadata(
|
|
33
|
-
response: Response,
|
|
34
|
-
transaction: HttpTransaction,
|
|
35
|
-
) {
|
|
36
|
-
transaction.apply {
|
|
37
|
-
// includes headers added later in the chain
|
|
38
|
-
requestHeadersSize = response.request.headers.byteCount()
|
|
39
|
-
setRequestHeaders(response.request.headers.redact(headersToRedact))
|
|
40
|
-
responseHeadersSize = response.headers.byteCount()
|
|
41
|
-
setResponseHeaders(response.headers.redact(headersToRedact))
|
|
42
|
-
|
|
43
|
-
requestDate = response.sentRequestAtMillis
|
|
44
|
-
responseDate = response.receivedResponseAtMillis
|
|
45
|
-
protocol = response.protocol.toString()
|
|
46
|
-
responseCode = response.code
|
|
47
|
-
responseMessage = response.message
|
|
48
|
-
|
|
49
|
-
response.handshake?.let { handshake ->
|
|
50
|
-
responseTlsVersion = handshake.tlsVersion.javaName
|
|
51
|
-
responseCipherSuite = handshake.cipherSuite.javaName
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
responseContentType = response.contentType
|
|
55
|
-
|
|
56
|
-
tookMs = (response.receivedResponseAtMillis - response.sentRequestAtMillis)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private fun multiCastResponse(
|
|
61
|
-
response: Response,
|
|
62
|
-
transaction: HttpTransaction,
|
|
63
|
-
): Response {
|
|
64
|
-
val responseBody = response.body
|
|
65
|
-
if (!response.hasBody() || responseBody == null) {
|
|
66
|
-
collector.onResponseReceived(transaction)
|
|
67
|
-
return response
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
val contentType = responseBody.contentType()
|
|
71
|
-
val contentLength = responseBody.contentLength()
|
|
72
|
-
|
|
73
|
-
val sideStream =
|
|
74
|
-
ReportingSink(
|
|
75
|
-
createTempTransactionFile(),
|
|
76
|
-
ResponseReportingSinkCallback(response, transaction),
|
|
77
|
-
maxContentLength,
|
|
78
|
-
)
|
|
79
|
-
var upstream: Source = TeeSource(responseBody.source(), sideStream)
|
|
80
|
-
if (alwaysReadResponseBody) upstream = DepletingSource(upstream)
|
|
81
|
-
|
|
82
|
-
return response.newBuilder()
|
|
83
|
-
.body(upstream.buffer().asResponseBody(contentType, contentLength))
|
|
84
|
-
.build()
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private fun createTempTransactionFile(): File? {
|
|
88
|
-
val cache = cacheDirectoryProvider.provide()
|
|
89
|
-
return if (cache == null) {
|
|
90
|
-
Logger.warn("Failed to obtain a valid cache directory for transaction files")
|
|
91
|
-
null
|
|
92
|
-
} else {
|
|
93
|
-
FileFactory.create(cache)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private fun processPayload(
|
|
98
|
-
response: Response,
|
|
99
|
-
payload: Buffer,
|
|
100
|
-
transaction: HttpTransaction,
|
|
101
|
-
) {
|
|
102
|
-
val responseBody = response.body ?: return
|
|
103
|
-
|
|
104
|
-
val contentType = responseBody.contentType()
|
|
105
|
-
|
|
106
|
-
val isImageContentType = contentType?.toString()?.contains(CONTENT_TYPE_IMAGE, ignoreCase = true) == true
|
|
107
|
-
if (isImageContentType) {
|
|
108
|
-
if (payload.size < MAX_BLOB_SIZE) {
|
|
109
|
-
transaction.responseImageData = payload.readByteArray()
|
|
110
|
-
}
|
|
111
|
-
} else if (payload.size != 0L) {
|
|
112
|
-
val decodedContent = decodePayload(response, payload.readByteString())
|
|
113
|
-
transaction.responseBody = decodedContent
|
|
114
|
-
transaction.isResponseBodyEncoded = decodedContent == null
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private fun decodePayload(
|
|
119
|
-
response: Response,
|
|
120
|
-
body: ByteString,
|
|
121
|
-
) = bodyDecoders.asSequence()
|
|
122
|
-
.mapNotNull { decoder ->
|
|
123
|
-
try {
|
|
124
|
-
decoder.decodeResponse(response, body)
|
|
125
|
-
} catch (e: IOException) {
|
|
126
|
-
Logger.warn("Decoder $decoder failed to process response payload", e)
|
|
127
|
-
null
|
|
128
|
-
}
|
|
129
|
-
}.firstOrNull()
|
|
130
|
-
|
|
131
|
-
private inner class ResponseReportingSinkCallback(
|
|
132
|
-
private val response: Response,
|
|
133
|
-
private val transaction: HttpTransaction,
|
|
134
|
-
) : ReportingSink.Callback {
|
|
135
|
-
override fun onClosed(
|
|
136
|
-
file: File?,
|
|
137
|
-
sourceByteCount: Long,
|
|
138
|
-
) {
|
|
139
|
-
file?.readResponsePayload()?.let { payload ->
|
|
140
|
-
processPayload(response, payload, transaction)
|
|
141
|
-
}
|
|
142
|
-
transaction.responsePayloadSize = sourceByteCount
|
|
143
|
-
collector.onResponseReceived(transaction)
|
|
144
|
-
file?.delete()
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
override fun onFailure(
|
|
148
|
-
file: File?,
|
|
149
|
-
exception: java.io.IOException,
|
|
150
|
-
) {
|
|
151
|
-
Logger.error("Failed to read response payload", exception)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private fun File.readResponsePayload() =
|
|
155
|
-
try {
|
|
156
|
-
source().uncompress(response.headers).use { source ->
|
|
157
|
-
Buffer().apply { writeAll(source) }
|
|
158
|
-
}
|
|
159
|
-
} catch (e: java.io.IOException) {
|
|
160
|
-
Logger.error("Response payload couldn't be processed", e)
|
|
161
|
-
null
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private companion object {
|
|
166
|
-
const val MAX_BLOB_SIZE = 1_000_000L
|
|
167
|
-
|
|
168
|
-
const val CONTENT_TYPE_IMAGE = "image"
|
|
169
|
-
}
|
|
170
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
package com.securitysuite.internal.support
|
|
2
|
-
|
|
3
|
-
import android.text.SpannableStringBuilder
|
|
4
|
-
import android.text.Spanned
|
|
5
|
-
import android.text.style.BackgroundColorSpan
|
|
6
|
-
import android.text.style.ForegroundColorSpan
|
|
7
|
-
import android.text.style.UnderlineSpan
|
|
8
|
-
import java.util.regex.Pattern
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Highlight parts of the String when it matches the search.
|
|
12
|
-
*
|
|
13
|
-
* @param search the text to highlight
|
|
14
|
-
*/
|
|
15
|
-
internal fun SpannableStringBuilder.highlightWithDefinedColors(
|
|
16
|
-
search: String,
|
|
17
|
-
startIndices: List<Int>,
|
|
18
|
-
backgroundColor: Int,
|
|
19
|
-
foregroundColor: Int,
|
|
20
|
-
): SpannableStringBuilder {
|
|
21
|
-
return applyColoredSpannable(this, startIndices, search.length, backgroundColor, foregroundColor)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
internal fun CharSequence.indicesOf(input: String): List<Int> =
|
|
25
|
-
Pattern.compile(input, Pattern.CASE_INSENSITIVE).toRegex()
|
|
26
|
-
.findAll(this)
|
|
27
|
-
.map { it.range.first }
|
|
28
|
-
.toCollection(mutableListOf())
|
|
29
|
-
|
|
30
|
-
internal fun SpannableStringBuilder.highlightWithDefinedColorsSubstring(
|
|
31
|
-
search: String,
|
|
32
|
-
startIndex: Int,
|
|
33
|
-
backgroundColor: Int,
|
|
34
|
-
foregroundColor: Int,
|
|
35
|
-
): SpannableStringBuilder {
|
|
36
|
-
return applyColoredSpannableSubstring(this, startIndex, search.length, backgroundColor, foregroundColor)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private fun applyColoredSpannableSubstring(
|
|
40
|
-
text: SpannableStringBuilder,
|
|
41
|
-
subStringStartPosition: Int,
|
|
42
|
-
subStringLength: Int,
|
|
43
|
-
backgroundColor: Int,
|
|
44
|
-
foregroundColor: Int,
|
|
45
|
-
): SpannableStringBuilder {
|
|
46
|
-
return text.apply {
|
|
47
|
-
setSpan(
|
|
48
|
-
UnderlineSpan(),
|
|
49
|
-
subStringStartPosition,
|
|
50
|
-
subStringStartPosition + subStringLength,
|
|
51
|
-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
52
|
-
)
|
|
53
|
-
setSpan(
|
|
54
|
-
ForegroundColorSpan(foregroundColor),
|
|
55
|
-
subStringStartPosition,
|
|
56
|
-
subStringStartPosition + subStringLength,
|
|
57
|
-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
58
|
-
)
|
|
59
|
-
setSpan(
|
|
60
|
-
BackgroundColorSpan(backgroundColor),
|
|
61
|
-
subStringStartPosition,
|
|
62
|
-
subStringStartPosition + subStringLength,
|
|
63
|
-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private fun applyColoredSpannable(
|
|
69
|
-
text: SpannableStringBuilder,
|
|
70
|
-
indexes: List<Int>,
|
|
71
|
-
length: Int,
|
|
72
|
-
backgroundColor: Int,
|
|
73
|
-
foregroundColor: Int,
|
|
74
|
-
): SpannableStringBuilder {
|
|
75
|
-
return text.apply {
|
|
76
|
-
indexes.forEach {
|
|
77
|
-
applyColoredSpannableSubstring(text, it, length, backgroundColor, foregroundColor)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
package com.securitysuite.internal.support
|
|
2
|
-
|
|
3
|
-
import android.app.Activity
|
|
4
|
-
import android.content.ClipData
|
|
5
|
-
import android.content.Context
|
|
6
|
-
import android.content.Intent
|
|
7
|
-
import android.net.Uri
|
|
8
|
-
import androidx.core.app.ShareCompat
|
|
9
|
-
import androidx.core.content.FileProvider
|
|
10
|
-
import kotlinx.coroutines.Dispatchers
|
|
11
|
-
import kotlinx.coroutines.withContext
|
|
12
|
-
import okio.BufferedSource
|
|
13
|
-
import okio.Source
|
|
14
|
-
import okio.buffer
|
|
15
|
-
import okio.sink
|
|
16
|
-
|
|
17
|
-
internal interface Sharable {
|
|
18
|
-
fun toSharableContent(context: Context): Source
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
internal fun Sharable.toSharableUtf8Content(context: Context) =
|
|
22
|
-
toSharableContent(context)
|
|
23
|
-
.buffer()
|
|
24
|
-
.use(BufferedSource::readUtf8)
|
|
25
|
-
|
|
26
|
-
internal suspend fun Sharable.shareAsUtf8Text(
|
|
27
|
-
activity: Activity,
|
|
28
|
-
intentTitle: String,
|
|
29
|
-
intentSubject: String,
|
|
30
|
-
): Intent {
|
|
31
|
-
val content = withContext(Dispatchers.Default) { toSharableUtf8Content(activity) }
|
|
32
|
-
return ShareCompat.IntentBuilder(activity)
|
|
33
|
-
.setType("text/plain")
|
|
34
|
-
.setChooserTitle(intentTitle)
|
|
35
|
-
.setSubject(intentSubject)
|
|
36
|
-
.setText(content)
|
|
37
|
-
.createChooserIntent()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
internal fun Sharable.writeToFile(
|
|
41
|
-
context: Context,
|
|
42
|
-
fileName: String,
|
|
43
|
-
): Uri? {
|
|
44
|
-
val cache = context.cacheDir
|
|
45
|
-
if (cache == null) {
|
|
46
|
-
Logger.warn("Failed to obtain a valid cache directory for file export")
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
val file = FileFactory.create(cache, fileName)
|
|
51
|
-
if (file == null) {
|
|
52
|
-
Logger.warn("Failed to create an export file")
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
val fileContent = toSharableContent(context)
|
|
57
|
-
file.sink().buffer().use { it.writeAll(fileContent) }
|
|
58
|
-
|
|
59
|
-
return FileProvider.getUriForFile(
|
|
60
|
-
context,
|
|
61
|
-
"${context.packageName}.com.securitysuite.provider",
|
|
62
|
-
file,
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
internal fun Sharable.shareAsFile(
|
|
67
|
-
activity: Activity,
|
|
68
|
-
fileName: String,
|
|
69
|
-
intentTitle: String,
|
|
70
|
-
intentSubject: String,
|
|
71
|
-
clipDataLabel: String,
|
|
72
|
-
): Intent? {
|
|
73
|
-
val uri = writeToFile(activity, fileName) ?: return null
|
|
74
|
-
val shareIntent =
|
|
75
|
-
ShareCompat.IntentBuilder(activity)
|
|
76
|
-
.setType(activity.contentResolver.getType(uri))
|
|
77
|
-
.setChooserTitle(intentTitle)
|
|
78
|
-
.setSubject(intentSubject)
|
|
79
|
-
.setStream(uri)
|
|
80
|
-
.intent
|
|
81
|
-
shareIntent.apply {
|
|
82
|
-
clipData = ClipData.newRawUri(clipDataLabel, uri)
|
|
83
|
-
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
84
|
-
}
|
|
85
|
-
return Intent.createChooser(shareIntent, intentTitle)
|
|
86
|
-
}
|