HttpURLConnection with manual CookieHandler not working - kotlin

Java's default CookieHandler is good for one instance on one program, but i need many instance to request.
so I coded manual Cookie Handler.
here is my code. (Unirest is needed cause of Cookie thing)
import kong.unirest.Cookie
class CookieJar {
val cookies = arrayListOf<Cookie>()
fun supply(url: String): String {
return cookies.filter { url.contains(it.domain?.trim('.') ?: ".") }
.joinToString(";") { "${it.name}=${it.value}" }
}
fun consume(headers: Map<String, List<String>>) {
val dist = cookies.toMutableList()
val setCookie = headers["Set-Cookie"]
val strings = headers["Set-Cookie2"]
if (setCookie != null) dist.addAll(setCookie.map { Cookie(it) })
if (strings != null) dist.addAll(strings.map { Cookie(it) })
cookies.clear()
cookies.addAll(dist.distinctBy { "${it.domain};${it.name}" })
}
}
and here is my example. (Jackson databind is needed to read json)
import com.fasterxml.jackson.databind.ObjectMapper
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import kotlin.system.exitProcess
private val jar = CookieJar()
private val defaultHeader = hashMapOf(
"Accept" to "*/*",
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
"Connection" to "keep-alive"
)
fun main() {
CookieHandler.setDefault(null)
val request = request("https://stackoverflow.com/", "GET")
?: throw RuntimeException("Request failed")
val fkeyJson = (request.body.split(Regex("\r?\n")).map { it.trim() }.find {
it.startsWith("<script type=\"application/json\" data-role=\"module-args\" data-module-name=\"Shared/options.mod\">")
}
?: throw RuntimeException("fkey not found"))
val fkeyJsonTree = ObjectMapper().readTree(
fkeyJson.drop("<script type=\"application/json\" data-role=\"module-args\" data-module-name=\"Shared/options.mod\">".length)
.dropLast("</script>".length)
)
val fkey = fkeyJsonTree["options"]?.get("user")?.get("fkey")?.asText()
?: throw RuntimeException("fkey not found")
request(
"https://stackoverflow.com/legal/consent-action",
"POST",
body = buildUrlEncoded("type" to "AcceptAllFromBanner", "fkey" to fkey, "bannerVersion" to "baseline")
)
?: throw RuntimeException("Request failed")
val thisShouldNotReturnCookie = request("https://stackoverflow.com/", "GET")
?: throw RuntimeException("Request Failed")
if (thisShouldNotReturnCookie.headers["Set-Cookie"] != null) {
println("Cookie received.") // this should never happen
exitProcess(-1)
}
}
fun buildUrlEncoded(vararg pairs: Pair<String, Comparable<*>>) =
pairs.joinToString("&") { "${URLEncoder.encode(it.first, "utf-8")}=${URLEncoder.encode("${it.second}", "utf-8")}" }
fun request(url: String, method: String, header: Map<String, String> = defaultHeader, body: String? = null): Response? {
var res: Response? = null
val connection = URL(url).openConnection() as HttpURLConnection
try {
connection.requestMethod = method
connection.doInput = true
header.forEach(connection::setRequestProperty)
connection.useCaches = true
val supply = jar.supply(url)
if (supply.isNotBlank())
connection.setRequestProperty("Cookie", URLEncoder.encode(supply, "utf-8"))
if (body != null) {
connection.doOutput = true
connection.outputStream.use {
it.write(body.toByteArray())
it.flush()
}
}
connection.connect()
val stream = if (connection.responseCode == 200) connection.inputStream else connection.errorStream
res = Response(connection.responseCode, String(stream.readBytes(), charset("UTF-8")), connection.headerFields)
jar.consume(connection.headerFields)
} catch (io: IOException) {
io.printStackTrace()
} finally {
connection.disconnect()
}
return res
}
data class Response(val status: Int, val body: String, val headers: Map<String, List<String>>)
This example is just click "Accept all cookies" to store cookies, and send main page to test cookie is stored.
This should print nothing and closes with 0 exit code. but it always printing "Cookie received." and exit with -1.
I tested something, and i found HttpURLConnection is removing my "Cookie" header.
How to keep cookies on my Reqeust?
By the way, I tested it with UnirestInstance, it works.
But i don't wanna use external library (Cookie will implemented as mine).
Example code: nothing prints and exit with 0 exit code.
Actually resulted: prints "Cookie received." and exit with -1 exit code.

Related

Problem with volley POST and response null from the server (Kotlin)

I got a problem with my Volley POST in kotlin :
When I use this following code, my application go on "Response.Listener", but the array is null, so when I try to display the information I've just send, I can only get "null".
May you guys help me? :)
There is the kotlin code :
private fun sendHtmlRequest(view: View){
val emailreq = view?.findViewById<EditText>(R.id.editText_email)
val pwdreq = view?.findViewById<EditText>(R.id.editText_password)
val email = emailreq.text.toString()
val pwd = pwdreq.text.toString()
val jsonobj = JSONObject()
var url = "https://www.dorian-roulet.com/testStage-master/mobileApp/testpostone.php"
jsonobj.put("mail", email)
jsonobj.put("pwd", pwd)
val que = Volley.newRequestQueue(context)
val req = JsonObjectRequest(Request.Method.POST, url, jsonobj,
Response.Listener { response ->
view.findViewById<TextView>(R.id.error_login)?.text = ("Ca marche $response $jsonobj")
println("Yessai")
}, Response.ErrorListener{
view.findViewById<TextView>(R.id.error_login)?.text = ("Ca marche pas mec $jsonobj")
println("Erreur")
}
)
que.add(req)
}
Now, there is the PHP code :
<?php
$reponse = array("mail" => $_POST["mail"], "pwd" => $_POST["pwd"]);
echo json_encode($reponse);
?>
I tried to use a GET to recieve this data from a GET array, but the problem still being...
And when I go on my server's logs, I cans ee that I have a POST request from my application (even if I can't see what's in the request).
I use this code to do a Login task, but atm, I just want to send a post request from my application.
Please help :)
EDIT: Your PHP code is NOT fine.
To get JSON data you should not use $_POST (even if it was send with post). Use this instead:
<?php
$data = json_decode(file_get_contents('php://input'), true);
$reponse = array("mail" => $data["mail"], "pwd" => $data["pwd"]);
echo json_encode($reponse);
?>
Here is a code that work for volley (it's not too different than what you have :-) ):
val jsonObjectRequest: JsonObjectRequest = object : JsonObjectRequest(Method.POST, url, **jsonobj**, Response.Listener { response: JSONObject ->
try {
val email = response.getString("email")
val password = response.getString("password")
} catch (e: JSONException) {
// catch/handle JSON error
} catch (e: Exception) {
// catch/handle other error
}
}, Response.ErrorListener { error: VolleyError ->
//Error Listener code
}) {
override fun getBodyContentType(): String {
return "application/x-www-form-urlencoded"
}
// you can override more functions here if needed (getHeader, etc.)
}
queue.add(jsonObjectRequest)
For your case it would likely give:
private fun sendHtmlRequest(view: View){
val emailreq = view?.findViewById<EditText>(R.id.editText_email)
val pwdreq = view?.findViewById<EditText>(R.id.editText_password)
val email = emailreq.text.toString()
val pwd = pwdreq.text.toString()
val jsonobj = JSONObject()
var url = "https://www.dorian-roulet.com/testStage-master/mobileApp/testpostone.php"
jsonobj.put("mail", email)
jsonobj.put("pwd", pwd)
val que = Volley.newRequestQueue(context)
val req = JsonObjectRequest(Request.Method.POST, url, jsonobj,
Response.Listener { response: JSONObject ->
val mailBack = response.getString("mail")
val pwdBack = response.getString("pwd")
view.findViewById<TextView>(R.id.error_login)?.text = ("Ca marche $response $mailBack - $pwdBack")
println("Yessai")
}, Response.ErrorListener{
view.findViewById<TextView>(R.id.error_login)?.text = ("Ca marche pas mec $jsonobj")
println("Erreur")
}
)
que.add(req)
}
There is the code I have after you send me what code I have to put inside :
(Still returns null values)
private fun sendHtmlRequest(view: View){
val emailreq = view?.findViewById<EditText>(R.id.editText_email)
val pwdreq = view?.findViewById<EditText>(R.id.editText_password)
val email = emailreq.text.toString()
val pwd = pwdreq.text.toString()
val jsonobj = JSONObject()
var url = "https://www.dorian-roulet.com/testStage-master/mobileApp/testpostone.php"
jsonobj.put("mail", email)
jsonobj.put("pwd", pwd)
val que = Volley.newRequestQueue(context)
val jsonObjectRequest = JsonObjectRequest(
Request.Method.POST, url, jsonobj,
Response.Listener { response: JSONObject ->
val emails = response.getString("mail")
val passwords = response.getString("pwd")
view.findViewById<TextView>(R.id.error_login)?.text = ("Ca marche $emails $passwords $response $jsonobj")
}, Response.ErrorListener {
view.findViewById<TextView>(R.id.error_login)?.text = ("Ca marche pas mec $jsonobj")
println("Erreur")
}) /*{
override fun getBodyContentType(): String {
return "application/x-www-form-urlencoded"
}
// you can override more functions here if needed (getHeader, etc.)
}*/
que.add(jsonObjectRequest)
}

Get response from server in JWT

I have the following code, where I validate the JWT token (with volley):
private fun validateToken(token: String) {
var queue = Volley.newRequestQueue(this)
val yourUrl = "https://mysite/wp-json/jwt-auth/v1/token/validate"
val parameters = JSONObject()
try {
parameters.put("username", "abc#test.com")
parameters.put("password", "12345678")
} catch (e: java.lang.Exception) {
}
val request: JsonObjectRequest =
object : JsonObjectRequest(
Method.POST, yourUrl, parameters,
Response.Listener { response -> Log.i("onResponse", response.toString()) },
Response.ErrorListener { error -> Log.e("onErrorResponse", error.toString()) }) {
#Throws(AuthFailureError::class)
override fun getHeaders(): Map<String, String> {
val headers: MutableMap<String, String> = HashMap()
// Basic Authentication
//String auth = "Basic " + Base64.encodeToString(CONSUMER_KEY_AND_SECRET.getBytes(), Base64.NO_WRAP);
headers["Authorization"] = "Bearer $token"
return headers
}
}
queue.add(request)
}
It works for me and I get the correct response from the server (in Log.i):
{"code":"jwt_auth_valid_token","data":{"status":200}}
My question is how in my code I do to be able to save the status: 200 in a variable so then it applies an ʻif status == 200` and if it is 200 then send it to another activity.
Add implementation 'com.google.code.gson:gson:2.8.6' to build.gradle(app)
Create Model.tk with:
data class dataServer (
#SerializedName("code") val code : String,
#SerializedName("data") val data : Data
)
data class Data (
#SerializedName("status") val status : Int
)
3. Update code:
private fun validateToken(token: String) {
var queue = Volley.newRequestQueue(this)
val yourUrl = "https://myweb/wp-json/jwt-auth/v1/token/validate"
val parameters = JSONObject()
try {
parameters.put("username", "abc#test.com")
parameters.put("password", "12345678")
} catch (e: java.lang.Exception) {
}
val request: JsonObjectRequest =
object : JsonObjectRequest(
Method.POST, yourUrl, parameters,
Response.Listener {
response ->
Log.i("onResponse", response.toString())
val gson = Gson()
val dataToken = gson.fromJson(response.toString(), dataServer::class.java)
val status = dataToken.data.status
println(status)
// use here if then
},
Response.ErrorListener { error -> Log.e("onErrorResponse", error.toString()) }) {
#Throws(AuthFailureError::class)
override fun getHeaders(): Map<String, String> {
val headers: MutableMap<String, String> = HashMap()
// Basic Authentication
//String auth = "Basic " + Base64.encodeToString(CONSUMER_KEY_AND_SECRET.getBytes(), Base64.NO_WRAP);
headers["Authorization"] = "Bearer $token"
return headers
}
}
queue.add(request)
}

Testing endpoint under auth with Ktor

I'm struggling to write tests for an endpoint that is under auth (token). In particular, when writing my test, I'm not able to chain the login request with my second request, despite providing the token received as part of the login request.
LoginEndpoint.kt
const val LOGIN_ENDPOINT = "$API_VERSION/login"
#Location(LOGIN_ENDPOINT)
class Login
fun Route.loginEndpoint(patientsRepository: Repository<Patient>, authProvider: AuthProvider) {
post<Login> {
val params = call.receive<Parameters>()
val userId = params["id"] ?: return#post call.respond(status = HttpStatusCode.BadRequest, message = "Missing user id")
val user = patientsRepository.get(userId)
if (user != null) {
val token = authProvider.generateToken(user.id!!)
call.respond(TextContent("{ \"token\": \"$token\" }", ContentType.Application.Json))
} else {
call.respond(status = HttpStatusCode.NotFound, message = "User with id $userId does not exist")
}
}
}
LoginEndpointTest.kt (passing, all good)
#Test
fun `given user exists then returns 200 and token`() {
val userId = "paco123"
val token = "magic_token_123"
withTestApplication({
givenTestModule()
}) {
every { authProvider.generateToken(userId) } answers { token }
givenPatientExists(userId)
with(handleRequest(HttpMethod.Post, "/$API_VERSION/login") {
addHeader("content-type", "application/x-www-form-urlencoded")
setBody("id=$userId")
}) {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("{ \"token\": \"magic_token_123\" }", response.content)
}
}
}
private fun Application.givenTestModule() {
module(
testing = true,
repositoryModule = TestingRepositoryModule,
authProvider = authProvider
)
}
Now, the problematic endpoint.
ProfileEndpoint.kt
const val PATIENTS_API_ENDPOINT = "$API_VERSION/profile"
#Location(PATIENTS_API_ENDPOINT)
class ProfileEndpoint
fun Route.profileEndpoint(patientsRepository: Repository<Patient>) {
authenticate("jwt") {
get<ProfileEndpoint> {
val apiUser: Patient = call.apiUser!!
val id = apiUser.id!!
val patient = patientsRepository.get(id)
when (patient != null) {
false -> call.respond(status = HttpStatusCode.NotFound, message = "Patient with id $id does not exist")
true -> call.respond(status = HttpStatusCode.OK, message = patient.map())
}
}
}
}
Finally, my failing test
ProfileEndpointTest.kt
#Test
fun `given user is logged in then returns 200 and profile`() {
val userId = "paco123"
val token = "magic_token_123"
withTestApplication({
givenTestModule()
}) {
every { authProvider.generateToken(userId) } answers { token }
givenPatientExists(userId)
handleRequest(HttpMethod.Post, "/$API_VERSION/login") {
addHeader("content-type", "application/x-www-form-urlencoded")
setBody("id=$userId")
}
handleRequest(HttpMethod.Get, "/$API_VERSION/profile") {
addHeader("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.OK, response.status())
}
}
}
The error is:
expected:<200 OK> but was:<401 Unauthorized>
Expected :200 OK
Actual :401 Unauthorized
AuthProvider.kt
open class AuthProvider(secret: String = System.getenv(Environment.JWT_SECRET)) {
private val algorithm = Algorithm.HMAC512(secret)
fun getVerifier(): JWTVerifier = JWT
.require(algorithm)
.withIssuer(APP_NAME)
.build()
fun generateToken(userId: String): String = JWT.create()
.withSubject(SUBJECT)
.withIssuer(APP_NAME)
.withClaim(CLAIM, userId)
.withExpiresAt(expiresAt())
.sign(algorithm)
private fun expiresAt() = Date(System.currentTimeMillis() + MILLIES_PER_DAY * TOKEN_DAYS_LENGTH)
}
val ApplicationCall.apiUser get() = authentication.principal<Patient>()
I've tried using cookiesSession like in this documentation's example https://ktor.io/servers/testing.html#preserving-cookies but it didn't work. Any help would be much appreciated.

Ktor HttpClient Mock Fails

I'm trying to create a Ktor (1.3.1) HttpClient Mock with JsonFeature like this:
#Test
fun mockFailure() = runBlocking {
val mock = MockEngine { call ->
respond("{}",
HttpStatusCode.OK,
headersOf("Content-Type", ContentType.Application.Json.toString()))
}
val client = HttpClient(mock) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
val resp = client.get<JsonObject>("dsf")
}
It appears to process it correctly, but then I get this error:
io.ktor.client.call.NoTransformationFoundException: No transformation found: class kotlinx.coroutines.io.ByteBufferChannel -> class kotlinx.serialization.json.JsonObject
with response from http://localhost/dsf:
status: 200 OK
response headers:
Content-Type: application/json
at io.ktor.client.call.HttpClientCall.receive(HttpClientCall.kt:79)
Try installing client content negotiation plugin in the HttpClient block:
val client = HttpClient(mock) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
//Here
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
}
Required dependencies implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
Json Serialization implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
Also change the last line to val resp = client.get<JsonObject>("dsf").body()

Kotlin -Coroutine request won't wait till first request finish

I have 2 requests: SIGNUP and SIGNUP_UPLOAD_AVATAR
#POST(SIGNUP)
fun registerUser(#QueryMap(encoded = true) userCredentials: HashMap<String, Any>): Deferred<Response<UserResponse>>
#Multipart
#POST(SIGNUP_UPLOAD_AVATAR) //SHOULD BE A PUT, DUE TO ONLY SEND FILE.
fun uploadAvatar(#Part file: MultipartBody.Part): Deferred<Response<ResponseBody>>
currently i decided to change for the use of COROUTINES however im having an issue, that is when the first post SIGNUP_UPLOAD_AVATAR start his request, i need to wait till it finish to go with the SIGNUP process. However the second coroutine start immediately, without asking if the first request finish or is still working.
This is my function:
fun getImageUrlCoRoutine(){
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), profilePicture)
val body = MultipartBody.Part.createFormData("file", "android.${getFileExtension(profilePicture)}", requestFile)
val service = APIService.create()
val request = service.uploadAvatar(body)
try {
GlobalScope.launch(Dispatchers.Main) {
val response = CoroutineUtil().retryIO (times = 3){ request.await() }
val responseCode = StatusCode(response.code()).description
when(responseCode){
StatusCode.Status.OK -> {
response.body()?.let {
it.let {
println("RESULT: " + it.string())
avatarUrl = it.string()
println("Avatar: " + avatarUrl)
registrationCoroutine(this) <- here goes for the second request(registration)
}
}
}
else -> {}
}
}
}catch (e: HttpException){
val responseCode = StatusCode(e.code()).description
when(responseCode){
StatusCode.Status.NotAcceptable -> {
}
else -> {
}
}
}catch (e: Throwable){
println("Houston we got a Coroutine Problem")
println(e)
}
}
fun registrationCoroutine(suspended: CoroutineScope) {
val service = APIService.create()
val data = HashMap<String, Any>()
data["email"] = email
data["phone"] = phoneNumber
data["username"] = email
data["password"] = password
data["fullName"] = fullname
data["token"] = ""
data["appName"] = BuildConfig.APP_NAME
data["avatarUrl"] = avatarUrl
data["deviceType"] = BuildConfig.PLATFORM
data["needsVerify"] = false
suspended.launch {
val request = service.registerUser(data)
try {
val response = CoroutineUtil().retryIO(times = 3) { request.await() }
val responseCode = StatusCode(response.code()).description
when(responseCode){
StatusCode.Status.OK -> {
response.body()?.let {
it.let {
println(it)
}
}
}
else -> {}
}
} catch (e: HttpException) {
val responseCode = StatusCode(e.code()).description
when(responseCode){
StatusCode.Status.NotAcceptable -> {}
else -> {}
}
} catch (e: Throwable) {
println("Houston we have a coroutine problem")
println(e)
}
}
}
and the responses that i get...
2019-06-25 08:41:28.858 19886-19886/com.multirequest.development
I/System.out: RESULT:
2019-06-25 08:41:28.859 19886-19886/com.multirequest.development
I/System.out: Avatar:
2019-06-25 08:41:28.880 19886-20735/com.multirequest.development
D/OkHttp: --> POST
http://myCustomURL.com/signup?deviceType=ANDROID&password=demdemdem&needsVerify=false&phone=+1123456789&avatarUrl=&appName=DEMO&fullName=demmmm&email=demm#gmail.com&username=demm#gmail.com&token=
201
And i need that when i get the AvatarURL the signup process begin...
Thanks :)