Skip to content

Commit d73e766

Browse files
author
zjyzip
committed
optimize
1 parent c498e94 commit d73e766

4 files changed

Lines changed: 66 additions & 105 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ android {
7373
minSdk = 26
7474
targetSdk = 36
7575
versionCode = calculateVersionCode()
76-
versionName = "4.3.5"
76+
versionName = "4.3.6"
7777

7878
vectorDrawables {
7979
useSupportLibrary = true

app/src/main/java/com/close/hook/ads/hook/network/NativeRequestHook.kt

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import com.google.common.cache.CacheBuilder
77
import de.robv.android.xposed.XposedBridge
88
import java.io.ByteArrayOutputStream
99
import java.nio.ByteBuffer
10-
import java.util.concurrent.ConcurrentMap
1110
import java.util.concurrent.TimeUnit
1211

1312
object NativeRequestHook {
@@ -25,11 +24,15 @@ object NativeRequestHook {
2524
private val h2RequestBuffers = CacheBuilder.newBuilder()
2625
.expireAfterAccess(3, TimeUnit.MINUTES)
2726
.build<String, ByteArrayOutputStream>()
28-
27+
2928
private val h2ResponseBuffers = CacheBuilder.newBuilder()
3029
.expireAfterAccess(3, TimeUnit.MINUTES)
3130
.build<String, ByteArrayOutputStream>()
3231

32+
private val h2OverflowedStreams = CacheBuilder.newBuilder()
33+
.expireAfterAccess(3, TimeUnit.MINUTES)
34+
.build<String, Boolean>()
35+
3336
fun init(enableNativeHook: Boolean) {
3437
if (isInitialized) return
3538
try {
@@ -73,7 +76,7 @@ object NativeRequestHook {
7376
var shouldBlock = false
7477

7578
val buffers = if (isWrite) RequestHook.requestBuffers else RequestHook.responseBuffers
76-
val buffer = (buffers as ConcurrentMap<Int, ByteArrayOutputStream>).computeIfAbsent(key) { ByteArrayOutputStream() }
79+
val buffer = buffers.computeIfAbsent(key) { ByteArrayOutputStream() }
7780

7881
try {
7982
synchronized(buffer) {
@@ -115,7 +118,7 @@ object NativeRequestHook {
115118
private fun processTcpData(fd: Int, isWrite: Boolean, data: ByteArray, address: String?, stack: String?): Boolean {
116119
var shouldBlock = false
117120
val buffers = if (isWrite) RequestHook.requestBuffers else RequestHook.responseBuffers
118-
val buffer = (buffers as ConcurrentMap<Int, ByteArrayOutputStream>).computeIfAbsent(fd) { ByteArrayOutputStream() }
121+
val buffer = buffers.computeIfAbsent(fd) { ByteArrayOutputStream() }
119122

120123
try {
121124
synchronized(buffer) {
@@ -160,16 +163,22 @@ object NativeRequestHook {
160163
if (!isWrite && !getCollectResponseBody()) return
161164

162165
val key = "${connId}_$streamId"
166+
val overflowKey = "$key:${if (isWrite) "request" else "response"}"
167+
if (h2OverflowedStreams.getIfPresent(overflowKey) != null) return
168+
163169
val dup = buffer.duplicate()
164170
val data = ByteArray(dup.remaining())
165171
dup.get(data)
166172

167173
val buffers = if (isWrite) h2RequestBuffers else h2ResponseBuffers
168174
val stream = buffers.get(key) { ByteArrayOutputStream() }
169-
175+
170176
synchronized(stream) {
171177
if (stream.size() + data.size <= MAX_BUFFER_SIZE) {
172178
stream.write(data)
179+
} else {
180+
stream.reset()
181+
h2OverflowedStreams.put(overflowKey, true)
173182
}
174183
}
175184
}
@@ -203,8 +212,22 @@ object NativeRequestHook {
203212
val mimeType = if (!contentEncoding.isNullOrEmpty()) "$contentType; encoding=$contentEncoding" else contentType
204213

205214
val key = "${connId}_$streamId"
206-
val reqBody = if (isComplete) h2RequestBuffers.asMap().remove(key)?.toByteArray() else null
207-
val respBody = if (isComplete) h2ResponseBuffers.asMap().remove(key)?.toByteArray() else null
215+
val requestOverflowKey = "$key:request"
216+
val responseOverflowKey = "$key:response"
217+
val reqBody = if (isComplete) {
218+
h2RequestBuffers.asMap().remove(key)
219+
?.takeIf { h2OverflowedStreams.getIfPresent(requestOverflowKey) == null }
220+
?.toByteArray()
221+
} else null
222+
val respBody = if (isComplete) {
223+
h2ResponseBuffers.asMap().remove(key)
224+
?.takeIf { h2OverflowedStreams.getIfPresent(responseOverflowKey) == null }
225+
?.toByteArray()
226+
} else null
227+
if (isComplete) {
228+
h2OverflowedStreams.invalidate(requestOverflowKey)
229+
h2OverflowedStreams.invalidate(responseOverflowKey)
230+
}
208231

209232
val info = BlockedRequest(
210233
requestType = " H2",

app/src/main/java/com/close/hook/ads/hook/network/RequestHook.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ object RequestHook {
4343
0L, TimeUnit.MILLISECONDS,
4444
LinkedBlockingQueue(500),
4545
{ runnable -> Thread(runnable, "AdClose-AsyncBroadcast").apply { isDaemon = true } },
46-
ThreadPoolExecutor.CallerRunsPolicy()
46+
ThreadPoolExecutor.DiscardOldestPolicy()
4747
)
4848

4949
private val cachedAppLabel: String by lazy {
@@ -76,27 +76,27 @@ object RequestHook {
7676

7777
internal val requestBuffers = CacheBuilder.newBuilder()
7878
.expireAfterAccess(3, TimeUnit.MINUTES)
79-
.build<Int, ByteArrayOutputStream>()
79+
.build<Any, ByteArrayOutputStream>()
8080
.asMap()
8181

8282
internal val responseBuffers = CacheBuilder.newBuilder()
8383
.expireAfterAccess(3, TimeUnit.MINUTES)
84-
.build<Int, ByteArrayOutputStream>()
84+
.build<Any, ByteArrayOutputStream>()
8585
.asMap()
8686

8787
internal val pendingRequests = CacheBuilder.newBuilder()
8888
.expireAfterAccess(3, TimeUnit.MINUTES)
89-
.build<Int, BlockedRequest>()
89+
.build<Any, BlockedRequest>()
9090
.asMap()
9191

9292
private val requestParsingStates = CacheBuilder.newBuilder()
9393
.expireAfterAccess(3, TimeUnit.MINUTES)
94-
.build<Int, ParsingState>()
94+
.build<Any, ParsingState>()
9595
.asMap()
9696

9797
private val responseParsingStates = CacheBuilder.newBuilder()
9898
.expireAfterAccess(3, TimeUnit.MINUTES)
99-
.build<Int, ParsingState>()
99+
.build<Any, ParsingState>()
100100
.asMap()
101101

102102
private data class ParsingState(
@@ -198,7 +198,7 @@ object RequestHook {
198198
return checkShouldBlockRequest(info)
199199
}
200200

201-
internal fun processRequestBuffer(key: Int, isHttps: Boolean): Boolean {
201+
internal fun processRequestBuffer(key: Any, isHttps: Boolean): Boolean {
202202
try {
203203
val state = requestParsingStates.computeIfAbsent(key) { ParsingState() }
204204

@@ -276,7 +276,7 @@ object RequestHook {
276276
return false
277277
}
278278

279-
internal fun processResponseBuffer(key: Int, param: XC_MethodHook.MethodHookParam?): Boolean {
279+
internal fun processResponseBuffer(key: Any, param: XC_MethodHook.MethodHookParam?): Boolean {
280280
try {
281281
val state = responseParsingStates.computeIfAbsent(key) { ParsingState() }
282282

@@ -419,7 +419,7 @@ object RequestHook {
419419
}
420420

421421
private fun completeAndDispatchRequest(
422-
key: Int,
422+
key: Any,
423423
requestInfo: BlockedRequest,
424424
headers: String,
425425
body: ByteArray?,

app/src/main/java/com/close/hook/ads/hook/network/RequestHookHandler.kt

Lines changed: 26 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ import de.robv.android.xposed.XposedHelpers
1515
import java.io.ByteArrayInputStream
1616
import java.io.ByteArrayOutputStream
1717
import java.io.IOException
18-
import java.net.ProtocolException
1918
import java.io.InputStream
20-
import java.lang.reflect.Field
21-
import java.net.HttpURLConnection
2219
import java.net.InetAddress
23-
import java.net.URL
20+
import java.net.ProtocolException
21+
import java.net.UnknownHostException
22+
import java.lang.reflect.Field
2423
import java.nio.ByteBuffer
2524
import java.util.UUID
2625
import java.util.concurrent.ConcurrentHashMap
@@ -137,7 +136,7 @@ internal object RequestHookHandler {
137136
"after"
138137
) { param ->
139138
if (RequestHook.processDnsRequest(param.args[0], param.result)) {
140-
param.result = null
139+
param.throwable = UnknownHostException("Blocked by AdClose: ${param.args[0]}")
141140
}
142141
}
143142
HookUtil.findAndHookMethod(
@@ -147,7 +146,7 @@ internal object RequestHookHandler {
147146
"after"
148147
) { param ->
149148
if (RequestHook.processDnsRequest(param.args[0], param.result)) {
150-
param.result = emptyArray<InetAddress>()
149+
param.throwable = UnknownHostException("Blocked by AdClose: ${param.args[0]}")
151150
}
152151
}
153152
}
@@ -164,14 +163,15 @@ internal object RequestHookHandler {
164163
}
165164

166165
val socket = XposedHelpers.getObjectField(param.thisObject, "socket")
166+
?: return@hookAllMethods
167167
if (socket is SSLSocket) return@hookAllMethods
168168

169169
val bytes = param.args[1] as ByteArray
170170
val offset = param.args[2] as Int
171171
val len = param.args[3] as Int
172172
if (len <= 0) return@hookAllMethods
173173

174-
val key = System.identityHashCode(socket)
174+
val key = socket
175175
val buffer = RequestHook.requestBuffers.computeIfAbsent(key) { ByteArrayOutputStream() }
176176
buffer.write(bytes, offset, len)
177177
if (RequestHook.processRequestBuffer(key, isHttps = false)) {
@@ -190,17 +190,30 @@ internal object RequestHookHandler {
190190
}
191191

192192
val socket = XposedHelpers.getObjectField(param.thisObject, "socket")
193+
?: return@hookAllMethods
193194
if (socket is SSLSocket) return@hookAllMethods
194195

195196
val bytes = param.args[1] as ByteArray
196197
val len = param.result as? Int ?: -1
197198
if (len <= 0) return@hookAllMethods
198199

199-
val key = System.identityHashCode(socket)
200+
val key = socket
200201
val buffer = RequestHook.responseBuffers.computeIfAbsent(key) { ByteArrayOutputStream() }
201202
buffer.write(bytes, 0, len)
202203
RequestHook.processResponseBuffer(key, param)
203204
}
205+
206+
// Remove buffer entries as soon as the socket closes.
207+
HookUtil.hookAllMethods(
208+
"java.net.Socket",
209+
"close",
210+
"after"
211+
) { param ->
212+
val key = param.thisObject
213+
RequestHook.requestBuffers.remove(key)
214+
RequestHook.responseBuffers.remove(key)
215+
RequestHook.pendingRequests.remove(key)
216+
}
204217
} catch (e: Throwable) {
205218
XposedBridge.log("$LOG_PREFIX Error setting up plain socket hook: ${e.message}")
206219
}
@@ -241,7 +254,7 @@ internal object RequestHookHandler {
241254
return@findAndHookMethod
242255
}
243256

244-
val key = connId.toInt() or Int.MIN_VALUE
257+
val key = param.thisObject
245258
val buffer = RequestHook.requestBuffers.computeIfAbsent(key) { ByteArrayOutputStream() }
246259
buffer.write(bytes)
247260
if (RequestHook.processRequestBuffer(key, isHttps = true)) {
@@ -290,7 +303,7 @@ internal object RequestHookHandler {
290303
return@findAndHookMethod
291304
}
292305

293-
val key = connId.toInt() or Int.MIN_VALUE
306+
val key = param.thisObject
294307
val buffer = RequestHook.responseBuffers.computeIfAbsent(key) { ByteArrayOutputStream() }
295308
buffer.write(bytes)
296309
RequestHook.processResponseBuffer(key, param)
@@ -311,6 +324,9 @@ internal object RequestHookHandler {
311324
try {
312325
val connId = System.identityHashCode(param.thisObject).toLong() or (1L shl 48)
313326
NativeRequestHook.freeH2Conn(connId)
327+
RequestHook.requestBuffers.remove(param.thisObject)
328+
RequestHook.responseBuffers.remove(param.thisObject)
329+
RequestHook.pendingRequests.remove(param.thisObject)
314330
} catch (e: Throwable) {
315331
XposedBridge.log("$LOG_PREFIX ConscryptEngine.closeInbound hook error: ${e.message}")
316332
}
@@ -350,10 +366,6 @@ internal object RequestHookHandler {
350366
param.result = emptyWebResponse
351367
return@hookAllMethods
352368
}
353-
354-
if (HookPrefs.getBoolean(HookPrefs.KEY_COLLECT_RESPONSE_BODY, false)) {
355-
sendParallelRequestForLogging(request)
356-
}
357369
},
358370
classLoader
359371
)
@@ -372,84 +384,10 @@ internal object RequestHookHandler {
372384
)
373385
}
374386

375-
private fun sendParallelRequestForLogging(request: WebResourceRequest) {
376-
val url = request.url
377-
if (url == null || (url.scheme != "http" && url.scheme != "https")) return
378-
379-
RequestHook.asyncBroadcastExecutor.execute {
380-
try {
381-
val urlConnection = URL(request.url.toString()).openConnection() as HttpURLConnection
382-
urlConnection.connectTimeout = 5000
383-
urlConnection.readTimeout = 10000
384-
urlConnection.requestMethod = request.method
385-
386-
request.requestHeaders.forEach { (key, value) ->
387-
urlConnection.setRequestProperty(key, value)
388-
}
389-
urlConnection.setRequestProperty("X-AdClose-Proxy", "true")
390-
391-
urlConnection.connect()
392-
393-
val responseCode = urlConnection.responseCode
394-
val responseMessage = urlConnection.responseMessage
395-
val responseHeaders = urlConnection.headerFields
396-
.entries.filter { it.key != null }
397-
.joinToString("\n") { "${it.key}: ${it.value.joinToString(", ")}" }
398-
val contentType = urlConnection.contentType
399-
val contentEncoding = urlConnection.contentEncoding
400-
401-
val responseBodyBytes = if (HookPrefs.getBoolean(HookPrefs.KEY_COLLECT_RESPONSE_BODY, false)) {
402-
val inputStream: InputStream? = try {
403-
urlConnection.inputStream
404-
} catch (e: IOException) {
405-
urlConnection.errorStream
406-
}
407-
inputStream?.use { input ->
408-
val buffer = ByteArrayOutputStream()
409-
TeeInputStream(input, buffer).use { it.readBytes() }
410-
buffer.toByteArray()
411-
}
412-
} else null
413-
414-
val mimeTypeWithEncoding = if (!contentEncoding.isNullOrEmpty()) {
415-
"$contentType; encoding=$contentEncoding"
416-
} else {
417-
contentType
418-
}
419-
420-
val formattedUrl = RequestHook.formatUrlWithoutQuery(url)
421-
val info = BlockedRequest(
422-
requestType = " Web",
423-
requestValue = formattedUrl,
424-
method = request.method,
425-
urlString = url.toString(),
426-
requestHeaders = request.requestHeaders.toString(),
427-
requestBody = null,
428-
responseCode = responseCode,
429-
responseMessage = responseMessage,
430-
responseHeaders = responseHeaders,
431-
responseBody = responseBodyBytes,
432-
responseBodyContentType = mimeTypeWithEncoding,
433-
stack = HookUtil.getFormattedStackTrace(),
434-
dnsHost = null,
435-
fullAddress = null,
436-
requestId = UUID.randomUUID().toString()
437-
)
438-
RequestHook.checkShouldBlockRequest(info)
439-
} catch (e: Throwable) {
440-
XposedBridge.log("$LOG_PREFIX Error in parallel WebView request: ${e.message}")
441-
}
442-
}
443-
}
444-
445387
private fun processWebRequest(request: Any?): Boolean {
446388
try {
447389
val webResourceRequest = request as? WebResourceRequest ?: return false
448390

449-
if (webResourceRequest.requestHeaders["X-AdClose-Proxy"] == "true") {
450-
return false
451-
}
452-
453391
val urlString = webResourceRequest.url?.toString() ?: return false
454392
val formattedUrl = RequestHook.formatUrlWithoutQuery(Uri.parse(urlString))
455393

0 commit comments

Comments
 (0)