2020-08-14 21:41:43 +02:00
/ * *
* ProjectLaogai
*
* Copyright 2019 - 2020 < seil0 @mosad . xyz >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston ,
* MA 02110 - 1301 , USA .
*
* /
2020-08-13 21:01:21 +02:00
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
2020-08-16 21:30:18 +02:00
import org.jsoup.HttpStatusException
2020-08-13 21:01:21 +02:00
import org.jsoup.Jsoup
2020-08-14 15:08:42 +02:00
import org.jsoup.nodes.Element
2020-08-13 21:01:21 +02:00
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
2020-08-23 14:27:41 +02:00
import org.mosad.seil0.projectlaogai.util.GradeSubject
2020-08-13 21:01:21 +02:00
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
2020-08-15 18:13:29 +02:00
import java.util.*
2020-08-13 21:01:21 +02:00
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
2020-08-15 18:13:29 +02:00
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
2020-08-13 21:01:21 +02:00
2020-08-14 15:08:42 +02:00
/ * *
2020-08-15 18:13:29 +02:00
* Parse the qispos site the get all needed data for the grades fragment
2020-08-14 15:08:42 +02:00
* /
2020-08-15 18:13:29 +02:00
class QISPOSParser ( val context : Context ) {
2020-08-13 21:01:21 +02:00
private val className = this . javaClass . name
private val baseURL = " https://notenverwaltung.hs-offenburg.de "
private val loginPath = " /qispos/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal "
2020-08-16 21:30:18 +02:00
/ * *
* check if qispos is available
* @return a http status code , 999 if no HttpStatusException supplied
* /
fun checkQISPOSStatus ( ) : Int {
return runBlocking {
withContext ( Dispatchers . IO ) {
val socketFactory = createSSLSocketFactory ( )
try {
val res = Jsoup . connect ( baseURL + loginPath )
. sslSocketFactory ( socketFactory )
. followRedirects ( true )
. referrer ( " https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0 " )
. execute ( )
res . statusCode ( )
} catch ( exHttp : HttpStatusException ) {
exHttp . statusCode
} catch ( ex : Exception ) {
Log . e ( className , " Error while checking status " , ex )
999
}
}
}
}
2020-08-15 18:13:29 +02:00
/ * *
* parse the html from readGrades ( )
* @return a SortedMap , each entry is a semester , each semester has a ArrayList with subjects
* /
2020-08-23 14:27:41 +02:00
fun parseGrades ( ) : SortedMap < String , ArrayList < GradeSubject > > {
val gradesMap = HashMap < String , ArrayList < GradeSubject > > ( )
2020-08-15 18:13:29 +02:00
val gradesListHtml = readGrades ( )
2020-08-14 15:08:42 +02:00
gradesListHtml ?. select ( " table > tbody > tr " ) ?. forEach {
2020-08-14 21:41:43 +02:00
val row = it . select ( " td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright " )
2020-08-14 15:08:42 +02:00
2020-08-14 21:41:43 +02:00
// only real subjects will be selected
if ( row . size >= 6 && row [ 0 ] . text ( ) . length >= 7 ) {
2020-08-23 14:27:41 +02:00
val subject = GradeSubject (
2020-08-14 15:08:42 +02:00
id = row [ 0 ] . text ( ) ,
name = row [ 1 ] . text ( ) ,
semester = row [ 2 ] . text ( ) ,
2020-08-14 21:41:43 +02:00
grade = if ( row [ 3 ] . text ( ) . isNotEmpty ( ) ) row [ 3 ] . text ( ) else row [ 4 ] . text ( ) ,
2020-08-14 15:08:42 +02:00
credits = row [ 5 ] . text ( )
)
if ( gradesMap . containsKey ( subject . semester ) ) {
gradesMap [ subject . semester ] !! . add ( subject )
} else {
gradesMap [ subject . semester ] = arrayListOf ( subject )
}
}
}
2020-08-15 18:13:29 +02:00
// return the sorted map
return gradesMap . toSortedMap ( compareBy < String > {
val oText = it . substringAfter ( " " )
2020-08-14 21:41:43 +02:00
2020-08-15 18:13:29 +02:00
if ( oText . contains ( " / " ) ) {
oText . substringBefore ( " / " ) . toInt ( ) + 0.5
} else {
oText . toDouble ( )
}
} . thenBy { it } )
2020-08-14 15:08:42 +02:00
}
/ * *
* read the grades html from qispos
* @return the grades list as html element or null
* /
2020-08-15 18:13:29 +02:00
private fun readGrades ( ) : Element ? {
2020-08-13 21:01:21 +02:00
val credentials = EncryptedPreferences . readCredentials ( context )
val username = credentials . first . substringBefore ( " @ " )
val password = credentials . second
2020-08-14 15:08:42 +02:00
return runBlocking {
2020-08-13 21:01:21 +02:00
withContext ( Dispatchers . IO ) {
try {
2020-08-15 18:13:29 +02:00
val socketFactory = createSSLSocketFactory ( )
2020-08-13 21:01:21 +02:00
// login, asdf = username, fdsa = password, wtf
val list = mapOf (
Pair ( " asdf " , username ) ,
Pair ( " fdsa " , password ) ,
Pair ( " submit " , " Anmelden " )
)
// login and get session cookies
val res = Jsoup . connect ( baseURL + loginPath )
. sslSocketFactory ( socketFactory )
. followRedirects ( true )
. referrer ( " https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0 " )
. data ( list )
. postDataCharset ( " UTF-8 " )
. execute ( )
2020-08-15 18:13:29 +02:00
Log . i ( className , " login status is: ${res.statusMessage()} : ${res.statusCode()} " )
2020-08-13 21:01:21 +02:00
val loginCookies = res . cookies ( )
2020-08-14 15:08:42 +02:00
// grades root document and url
val rootHtml = Jsoup . parse ( res . body ( ) )
val gradesRootLink = rootHtml . select ( " li.menueListStyle > a.auflistung " ) . last ( ) . attr ( " abs:href " )
// parse grades url
val gradesHtml = Jsoup . connect ( gradesRootLink )
. sslSocketFactory ( socketFactory )
. followRedirects ( true )
. cookies ( loginCookies )
. referrer ( " https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0 " )
. get ( )
val gradesNextLink = gradesHtml . select ( " li.treelist > a.regular " ) . attr ( " abs:href " )
val gradesNextHtml = Jsoup . connect ( gradesNextLink )
. sslSocketFactory ( socketFactory )
. followRedirects ( true )
. cookies ( loginCookies )
. referrer ( " https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0 " )
. get ( )
val gradesListLink = gradesNextHtml . selectFirst ( " li.treelist > ul > li " ) . selectFirst ( " a " ) . attr ( " abs:href " )
// get the grades list
val gradesListHtml = Jsoup . connect ( gradesListLink )
. sslSocketFactory ( socketFactory )
. followRedirects ( true )
. cookies ( loginCookies )
. referrer ( " https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0 " )
. get ( )
2020-08-13 21:01:21 +02:00
2020-08-14 15:08:42 +02:00
gradesListHtml . body ( )
2020-08-13 21:01:21 +02:00
} catch ( ex : Exception ) {
2020-08-15 18:13:29 +02:00
Log . e ( className , " error while loading qispos " , ex )
2020-08-14 15:08:42 +02:00
null
2020-08-13 21:01:21 +02:00
}
}
}
}
2020-08-15 18:13:29 +02:00
/ * *
* since the HS has a fucked up tls setup we need to work around that
* /
private fun createSSLSocketFactory ( ) : SSLSocketFactory {
2020-08-13 21:01:21 +02:00
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
val cf : CertificateFactory = CertificateFactory . getInstance ( " X.509 " )
val caInput = context . resources . openRawResource ( R . raw . notenverwaltung _hs _offenburg _de )
val ca = caInput . use {
cf . generateCertificate ( it ) as X509Certificate
}
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore . getDefaultType ( )
val keyStore = KeyStore . getInstance ( keyStoreType ) . apply {
load ( null , null )
setCertificateEntry ( " ca " , ca )
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm = TrustManagerFactory . getDefaultAlgorithm ( )
val tmf = TrustManagerFactory . getInstance ( tmfAlgorithm ) . apply {
init ( keyStore )
}
// Create an SSLContext that uses our TrustManager
val sslContext = SSLContext . getInstance ( " TLS " ) . apply {
init ( null , tmf . trustManagers , null )
}
return sslContext . socketFactory
}
}