1- @file:OptIn(kotlinx.cinterop.ExperimentalForeignApi ::class )
2-
31package org.ntqqrev.acidify.internal.util
42
5- import kotlinx.cinterop.*
6- import platform.posix.X_OK
7- import platform.posix.access
8- import platform.posix.errno
9- import platform.posix.fclose
10- import platform.posix.fopen
11- import platform.posix.fread
12- import platform.posix.fwrite
13- import platform.posix.getenv
14- import platform.posix.readlink
15- import platform.posix.remove
16- import platform.posix.stat
17- import platform.posix.strerror
18- import platform.posix.system
19- import kotlin.random.Random
3+ import org.ntqqrev.androidhttps.executeTextRequest
204
215internal actual fun platformCurlTextRequestOrNull (
226 method : String ,
@@ -27,158 +11,20 @@ internal actual fun platformCurlTextRequestOrNull(
2711 followRedirects : Boolean ,
2812 proxy : String? ,
2913): PlatformCurlTextResponse ? {
30- val curlBinary = discoverCurlBinary()
31- val stdoutPath = createTempPath(" curl-stdout" )
32- val stderrPath = createTempPath(" curl-stderr" )
33- val headerPath = createTempPath(" curl-headers" )
34- val bodyPath = createTempPath(" curl-body" )
35- val inputPath = body?.let {
36- createTempPath(" curl-input" ).also { path -> writeTextFile(path, body) }
37- }
38- return try {
39- val args = buildList {
40- add(curlBinary)
41- add(" --silent" )
42- add(" --show-error" )
43- add(" --http1.1" )
44- add(" --request" )
45- add(method)
46- add(" --dump-header" )
47- add(headerPath)
48- add(" --output" )
49- add(bodyPath)
50- add(" --write-out" )
51- add(" %{http_code}" )
52- if (followRedirects) {
53- add(" --location" )
54- }
55- if (! proxy.isNullOrBlank()) {
56- add(" --proxy" )
57- add(proxy)
58- }
59- if (! contentType.isNullOrBlank()) {
60- add(" --header" )
61- add(" Content-Type: $contentType " )
62- }
63- headers.forEach { (key, value) ->
64- add(" --header" )
65- add(" $key : $value " )
66- }
67- if (inputPath != null ) {
68- add(" --data-binary" )
69- add(" @$inputPath " )
70- }
71- add(url)
72- }
73- val status = decodePosixStatus(system(buildRedirectedCommand(args, stdoutPath, stderrPath)))
74- val stdout = readTextFile(stdoutPath).trim()
75- val stderr = readTextFile(stderrPath).trim()
76- if (status != 0 ) {
77- val message = stderr.ifBlank { " curl exited with code $status " }
78- throw IllegalStateException (message)
79- }
80- PlatformCurlTextResponse (
81- statusCode = stdout.toIntOrNull() ? : - 1 ,
82- headers = parseHeaders(readTextFile(headerPath)),
83- body = readTextFile(bodyPath),
84- )
85- } finally {
86- listOf (stdoutPath, stderrPath, headerPath, bodyPath, inputPath)
87- .filterNotNull()
88- .forEach { path -> remove(path) }
89- }
90- }
91-
92- private fun discoverCurlBinary (): String {
93- getenv(" YOGURT_CURL_PATH" )?.toKString()?.takeIf { access(it, X_OK ) == 0 }?.let { return it }
94- getenv(" ACIDIFY_CURL_PATH" )?.toKString()?.takeIf { access(it, X_OK ) == 0 }?.let { return it }
95-
96- currentProgramDirectory()?.let { programDir ->
97- val candidate = " $programDir /curl"
98- if (access(candidate, X_OK ) == 0 ) {
99- return candidate
100- }
101- }
102-
103- if (access(" ./curl" , X_OK ) == 0 ) {
104- return " ./curl"
105- }
106- if (access(" /system/bin/curl" , X_OK ) == 0 ) {
107- return " /system/bin/curl"
108- }
109- return " curl"
110- }
111-
112- private fun currentProgramDirectory (): String? = memScoped {
113- val bufferSize = 4096
114- val buffer = allocArray<ByteVar >(bufferSize)
115- val length = readlink(" /proc/self/exe" , buffer, (bufferSize - 1 ).convert())
116- if (length <= 0 ) return @memScoped null
117- buffer[length] = 0
118- buffer.toKString().substringBeforeLast(' /' , " " ).ifBlank { null }
119- }
120-
121- private fun createTempPath (kind : String ): String =
122- " /data/local/tmp/acidify-$kind -${Random .nextLong().toULong().toString(16 )} .tmp"
123-
124- private fun buildRedirectedCommand (args : List <String >, stdoutPath : String , stderrPath : String ): String =
125- buildString {
126- append(args.joinToString(" " ) { quotePosixArgument(it) })
127- append(" > " )
128- append(quotePosixArgument(stdoutPath))
129- append(" 2> " )
130- append(quotePosixArgument(stderrPath))
131- }
132-
133- private fun quotePosixArgument (argument : String ): String =
134- " '" + argument.replace(" '" , " '\" '\" '" ) + " '"
135-
136- private fun decodePosixStatus (status : Int ): Int = when {
137- status < 0 -> - 1
138- (status and 0x7f ) == 0 -> (status ushr 8 ) and 0xff
139- (status and 0x7f ) != 0x7f -> 128 + (status and 0x7f )
140- else -> - 1
141- }
142-
143- private fun writeTextFile (path : String , text : String ) {
144- val file = fopen(path, " wb" ) ? : error(strerror(errno)?.toKString() ? : " Failed to open $path " )
145- try {
146- val bytes = text.encodeToByteArray()
147- if (bytes.isNotEmpty()) {
148- bytes.usePinned {
149- fwrite(it.addressOf(0 ), 1 .convert(), bytes.size.convert(), file)
150- }
151- }
152- } finally {
153- fclose(file)
154- }
155- }
156-
157- private fun readTextFile (path : String ): String = memScoped {
158- val st = alloc< stat> ()
159- if (platform.posix.stat(path, st.ptr) != 0 ) return @memScoped " "
160- val size = st.st_size.toInt()
161- if (size <= 0 ) return @memScoped " "
162- val file = fopen(path, " rb" ) ? : return @memScoped " "
163- try {
164- val bytes = ByteArray (size)
165- bytes.usePinned {
166- fread(it.addressOf(0 ), 1 .convert(), bytes.size.convert(), file)
167- }
168- bytes.decodeToString()
169- } finally {
170- fclose(file)
171- }
172- }
173-
174- private fun parseHeaders (rawHeaders : String ): Map <String , List <String >> {
175- val result = linkedMapOf<String , MutableList <String >>()
176- rawHeaders.lineSequence().forEach { line ->
177- val separatorIndex = line.indexOf(' :' )
178- if (separatorIndex <= 0 ) return @forEach
179- val key = line.substring(0 , separatorIndex).trim().lowercase()
180- val value = line.substring(separatorIndex + 1 ).trim()
181- result.getOrPut(key) { mutableListOf () }.add(value)
182- }
183- return result
14+ if (! proxy.isNullOrBlank()) {
15+ return null
16+ }
17+ val response = executeTextRequest(
18+ method = method,
19+ url = url,
20+ headers = headers,
21+ body = body,
22+ contentType = contentType,
23+ followRedirects = followRedirects,
24+ )
25+ return PlatformCurlTextResponse (
26+ statusCode = response.statusCode,
27+ headers = response.headers,
28+ body = response.body,
29+ )
18430}
0 commit comments