package edu.purdue.app.schedule;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class Schedule {
private UserInfo userInfo;
private List<Class> classes;
//all the cookies we need to get the schedule (may include not needed ones)
private final static String[] okCookies = new String[] {
"UserAgentId",
"BIGipServer~BAN~pool_wl.mypurdue_443",
"fos.web.server",
"fos.secure.web.server",
"runId",
"JSESSIONID_LP4",
"usid",
"usidsec",
"SESSID",
"TESTID",
"BANSSO",
"CPSESSID"
};
//day map
private final static int[] days = new int[] {
Calendar.MONDAY,
Calendar.TUESDAY,
Calendar.WEDNESDAY,
Calendar.THURSDAY,
Calendar.FRIDAY,
Calendar.SATURDAY,
Calendar.SUNDAY
};
/**
*
* Set up a connection to a url (helper function)
* @param url Url of request
* @param host Host header for request
* @return connection you requested
*/
private static HttpsURLConnection setup(String url, String host) throws Exception {
HttpsURLConnection conn =
(HttpsURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
conn.setRequestProperty("Connection", "keep-alive");
conn.setRequestProperty("Host", host);
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0");
return conn;
}
/**
*Set up a connection to a url (helper function)
* @param url Url of request
* @param host host header for request
* @param referer referer header
* @param cookies Cookie structure to be converted to Cookie header
* @return connection you requested
*/
private static HttpsURLConnection setup(String url, String host, String referer, Cookies cookies) throws Exception {
HttpsURLConnection conn = setup(url, host);
conn.setRequestProperty("Referer", referer);
conn.setRequestProperty("Cookie", cookies.toString());
return conn;
}
/**
*Get contents of a connection (helper function)
* @param conn connection
* @return contents of response as 1 string
*/
private static String getContents(HttpsURLConnection conn) throws Exception {
Scanner in = new Scanner(conn.getInputStream());
in.useDelimiter("\\Z");
String content = in.next();
in.close();
return content;
}
public Schedule(String user, String password) throws Exception {
classes = new ArrayList<Class>();
parseRawSchedule(getRawSchedule(user, password));
}
private String getRawSchedule(String user, String password) throws Exception {
//extract login cookies from page
HttpsURLConnection conn = setup("https://wl.mypurdue.purdue.edu/cp/home/displaylogin", "wl.mypurdue.purdue.edu");
Cookies cookies = new Cookies(okCookies, conn.getHeaderFields());
String contents = getContents(conn);
//extract clientServerDelta (current time - constant) from page
Pattern pattern = Pattern.compile("[0-9]+;");
Matcher matcher = pattern.matcher(contents);
long clientServerDelta;
if(matcher.find()) {
String rawMatch = matcher.group();
String constant = rawMatch.substring(0, rawMatch.length() - 1);
clientServerDelta = System.currentTimeMillis() - Long.parseLong(constant);
}
else {
throw new Exception("couldn't find clientServerDelta");
}
//post login info to next step
conn = setup("https://wl.mypurdue.purdue.edu/cp/home/login", "wl.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/cp/home/displaylogin", cookies);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//simulate uuid calculation (submit time - clientServerDelta) with sleep
Random r = new Random();
long time = r.nextInt(10000);
Thread.sleep(time);
long uuid = System.currentTimeMillis() - clientServerDelta;
String content = "pass=" + password + "&user=" + user + "&uuid=" + uuid;
conn.setRequestProperty("Content-Length", Integer.toString(content.length()));
OutputStream out = conn.getOutputStream();
PrintWriter writer = new PrintWriter(out);
writer.print(content);
writer.close();
out.close();
//check for failure
if(conn.getResponseCode() != 302 || !conn.getHeaderFields().get("Location").get(0).equals("https://wl.mypurdue.purdue.edu/cps/welcome/loginok.html")) {
throw new Exception("failed to login");
}
//go through login steps
cookies.setCookies(conn.getHeaderFields());
conn = setup("https://wl.mypurdue.purdue.edu/cps/welcome/loginok.html", "wl.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/cp/home/login", cookies);
String response = getContents(conn);
if(!response.contains("https://wl.mypurdue.purdue.edu/cp/home/next")) {
throw new Exception("Failed to get next");
}
conn = setup("https://wl.mypurdue.purdue.edu/cp/home/next", "wl.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/cps/welcome/loginok.html", cookies);
if(conn.getResponseCode() != 302 || !conn.getHeaderFields().get("Location").get(0).equals("https://wl.mypurdue.purdue.edu/render.userLayoutRootNode.uP?uP_root=root")) {
throw new Exception("Failed to get root page!");
}
conn = setup("https://wl.mypurdue.purdue.edu/render.userLayoutRootNode.uP?uP_root=root", "wl.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/cps/welcome/loginok.html", cookies);
response = getContents(conn);
//set up sctSession cookie (increases with time)
cookies.setCookie("sctSession", Integer.toString(r.nextInt(5) + 1));
//go through steps to get week at a glance
conn = setup("https://wl.mypurdue.purdue.edu/jsp/misc/ss_redir.jsp?pg=24", "wl.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/tag.7cee4c7293654c6f.render.userLayoutRootNode.uP?uP_root=root&uP_sparam=activeTab&activeTab=u12l1s2&uP_tparam=frm&frm=", cookies);
cookies.setCookie("sctSession", Integer.toString(8 + r.nextInt(5)));
conn = setup("https://wl.mypurdue.purdue.edu/cp/ip/login?sys=sctssb&url=https://selfservice.mypurdue.purdue.edu/prod/tzwkwbis.P_CheckAgreeAndRedir?ret_code=STU_WEEKGLANCE", "wl.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/render.UserLayoutRootNode.uP?uP_tparam=utf&utf=%2fcp%2fip%2flogin%3fsys%3dsctssb%26url%3dhttps://selfservice.mypurdue.purdue.edu/prod/tzwkwbis.P_CheckAgreeAndRedir?ret_code=STU_WEEKGLANCE", cookies);
if(conn.getHeaderFields().get("Location") == null) {
throw new Exception("unable to get schedule setup!");
}
String url = conn.getHeaderFields().get("Location").get(0);
conn = setup(url, "selfservice.mypurdue.purdue.edu", " https://wl.mypurdue.purdue.edu/render.UserLayoutRootNode.uP?uP_tparam=utf&utf=%2fcp%2fip%2flogin%3fsys%3dsctssb%26url%3dhttps://selfservice.mypurdue.purdue.edu/prod/tzwkwbis.P_CheckAgreeAndRedir?ret_code=STU_WEEKGLANCE", cookies);
cookies.setCookies(conn.getHeaderFields());
conn = setup("https://selfservice.mypurdue.purdue.edu/prod/tzwkwbis.P_CheckAgreeAndRedir?ret_code=STU_WEEKGLANCE", "selfservice.mypurdue.purdue.edu", "https://wl.mypurdue.purdue.edu/render.UserLayoutRootNode.uP?uP_tparam=utf&utf=%2fcp%2fip%2flogin%3fsys%3dsctssb%26url%3dhttps://selfservice.mypurdue.purdue.edu/prod/tzwkwbis.P_CheckAgreeAndRedir?ret_code=STU_WEEKGLANCE", cookies);
cookies.setCookies(conn.getHeaderFields());
//finally actually get week at a glance
conn = setup("https://selfservice.mypurdue.purdue.edu/prod/bwskfshd.P_CrseSchd", "selfservice.mypurdue.purdue.edu", "https://selfservice.mypurdue.purdue.edu/prod/tzwkwbis.P_CheckAgreeAndRedir?ret_code=STU_WEEKGLANCE", cookies);
return getContents(conn);
}
private void parseRawSchedule(String rawSchedule) throws Exception {
Document doc = Jsoup.parse(rawSchedule);
Elements elems = doc.select("[class=staticheaders]");
if(elems.size() != 0) {
userInfo = new UserInfo(elems.first().text());
}
elems = doc.select("[class=datadisplaytable]");
if(elems.size() != 0) {
Elements data = elems.first().select("tr");
//keep track of occupied rows
boolean tableLayout[][] = new boolean[7][90];
int curRow = 0;
int curCol = 0;
for(int k = 1; k < data.size(); ++k) {
Element e = data.get(k);
Elements rawClasses = e.children().select("td");
for(int i = 0; i < rawClasses.size(); ++i) {
Element curClass = rawClasses.get(i);
while(tableLayout[curCol][curRow]) {
curCol++;
if(curCol == 7) {
curCol = 0;
curRow++;
}
}
//if this is a class label
if(curClass.className().equals("ddlabel")) {
//occupy rowspan rows in the current column if specified
if(curClass.attr("rowspan") != null || curClass.attr("rowspan").length() != 0) {
Integer rowspan = Integer.parseInt(curClass.attr("rowspan"));
int addRow = curRow;
for(int j = 0; j < rowspan; ++j) {
tableLayout[curCol][addRow] = true;
addRow++;
}
}
classes.add(new Class(days[curCol], curClass.text()));
}
//the current row and col are always occupied
tableLayout[curCol][curRow] = true;
}
}
}
}
public List<Class> getClasses() {
return Collections.unmodifiableList(classes);
}
public UserInfo getUserInfo() {
return userInfo;
}
}