/** * ProjectLaogai * * Copyright 2019-2020 * * 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. * */ 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 import org.jsoup.Jsoup import org.jsoup.nodes.Element import org.mosad.seil0.projectlaogai.R import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences import org.mosad.seil0.projectlaogai.util.gradeSubject import java.security.KeyStore import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManagerFactory /** * TODO context in constructor? */ class QISPOSParser { 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" //private val rootPath = "/qispos/rds?state=change&type=1&moduleParameter=studyPOSMenu&nextdir=change&next=menu.vm&subdir=applications&xml=menu&purge=y&navigationPosition=functions%2CstudyPOSMenu&breadcrumb=studyPOSMenu&topitem=functions&subitem=studyPOSMenu" fun parseGrades(context: Context): HashMap> { val gradesMap = HashMap>() val gradesListHtml = readGrades(context) gradesListHtml?.select("table > tbody > tr")?.forEach { //val row = it.select("td.qis_konto") val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright") //println("-----------------------------------------------------------") //println(it.select("td.qis_konto")) //if(row.size >= 6) { // only real subjects will be selected if(row.size >= 6 && row[0].text().length >=7) { val subject = gradeSubject( id = row[0].text(), name = row[1].text(), semester = row[2].text(), grade = if (row[3].text().isNotEmpty()) row[3].text() else row[4].text(), credits = row[5].text() ) if (gradesMap.containsKey(subject.semester)) { gradesMap[subject.semester]!!.add(subject) println(subject.name) } else { gradesMap[subject.semester] = arrayListOf(subject) println(subject.name) } println("ID: ${row[0].text()}") println("Subject Name: ${row[1].text()}") println("Semester: ${row[2].text()}") println("Grade: ${row[3].text()}") println("Credits: ${row[5].text()}") println("------------------------------------------------------") } } println(gradesMap) gradesMap.forEach { println("${it.key}: ${it.value}") } // TODO sort println("finished parsing!") return gradesMap } /** * read the grades html from qispos * @return the grades list as html element or null */ private fun readGrades(context: Context): Element?{ val credentials = EncryptedPreferences.readCredentials(context) val username = credentials.first.substringBefore("@") val password = credentials.second return runBlocking { withContext(Dispatchers.IO) { try { println("querying qispos ...") println("using $username with ********") val socketFactory = createSSLSocketFactory(context) // 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() val loginCookies = res.cookies() println("cookies: $loginCookies") println("status is: ${res.statusMessage()}: ${res.statusCode()}") // grades root document and url val rootHtml =Jsoup.parse(res.body()) val gradesRootLink = rootHtml.select("li.menueListStyle > a.auflistung").last().attr("abs:href") println(gradesRootLink) // 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") println(gradesNextLink) 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") println(gradesListLink) // 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() //println(gradesListHtml.body()) gradesListHtml.body() } catch (ex: Exception) { Log.e(className, "could not load qispos", ex) null } } } } private fun createSSLSocketFactory(context: Context): SSLSocketFactory { // 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 } println("ca=" + ca.subjectDN) // 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 } }