diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java index 3d36871..5b8baf5 100644 --- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java @@ -1,428 +1,392 @@ /* BusTO - Backend components Copyright (C) 2018 Fabio Mazza 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, see . */ package it.reyboz.bustorino.backend; import android.support.annotation.Nullable; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.atomic.AtomicReference; public class FiveTAPIFetcher implements ArrivalsFetcher{ - private static final String SECRET_KEY="759C97DC7D115966C30FD9169BB200D9"; private static final String DEBUG_NAME = "FiveTAPIFetcher"; + private final Map defaultHeaders = getDefaultHeaders(); final static LinkedList apiDays = new LinkedList<>(Arrays.asList("dom","lun","mar","mer","gio","ven","sab")); @Override public Palina ReadArrivalTimesAll(String stopID, AtomicReference res) { //set the date for the request as now Palina p = new Palina(stopID); //request parameters String response = performAPIRequest(QueryType.ARRIVALS,stopID,res); if(response==null) { if(res.get()==result.SERVER_ERROR_404) { Log.w(DEBUG_NAME,"Got 404, either the server failed, or the stop was not found, or the hack is not working anymore"); res.set(result.EMPTY_RESULT_SET); } return p; } try { List routes = parseArrivalsServerResponse(response, res); for(Route r: routes){ p.addRoute(r); } } catch (JSONException ex){ res.set(result.PARSER_ERROR); return null; } res.set(result.OK); p.sortRoutes(); return p; } List parseArrivalsServerResponse(String JSONresponse, AtomicReference res) throws JSONException{ ArrayList routes = new ArrayList<>(3); /* Slight problem: "longName": ==> DESCRIPTION "name": "13N", "departures": [ { "arrivalTimeInt": 1272, "time": "21:12", "rt": false }] "lineType": "URBANO" ==> URBANO can be either bus or tram or METRO */ JSONArray arr; try{ arr = new JSONArray(JSONresponse); String type; Route.Type routetype; for(int i =0; i parseDirectionsFromResponse(String response) throws IllegalArgumentException,JSONException{ if(response == null || response.length()==0) throw new IllegalArgumentException("Response string is null or void"); ArrayList routes = new ArrayList<>(10); JSONArray lines =new JSONArray(response); for(int i=0; i 1) { String secondo = exploded[exploded.length-2]; if (secondo.contains("festivo")) { festivo = Route.FestiveInfo.FESTIVO; } else if (secondo.contains("feriale")) { festivo = Route.FestiveInfo.FERIALE; } else if(secondo.contains("lun. - ven")) { serviceDays = Route.reduced_week; } else if(secondo.contains("sab - fest.")){ serviceDays = Route.weekend; festivo = Route.FestiveInfo.FESTIVO; } else { /* Log.d(DEBUG_NAME,"Parsing details of line "+lineName+" branchid "+branchid+":\n\t"+ "Couldn't find a the service days\n"+ "Description: "+secondo+","+description ); */ } if(exploded.length>2){ switch (exploded[exploded.length-3].trim()) { case "bus": t = Route.Type.BUS; break; case "tram": //never happened, but if it could happen you can get it t = Route.Type.TRAM; break; default: //nothing } } } else //only one piece if(description.contains("festivo")){ festivo = Route.FestiveInfo.FESTIVO; } else if(description.contains("feriale")){ festivo = Route.FestiveInfo.FERIALE; } if(t == Route.Type.UNKNOWN &&(lineName.trim().equals("10")|| lineName.trim().equals("15"))) t= Route.Type.TRAM; if(direction.contains("-")){ //Sometimes the actual filtered direction still remains the full line (including both extremes) direction = direction.split("-")[1]; } Route r = new Route(lineName.trim(),direction.trim(),t,new ArrayList<>()); if(serviceDays.length>0) r.serviceDays = serviceDays; r.festivo = festivo; r.branchid = branchid; r.description = description.trim(); r.setStopsList(Arrays.asList(stops.split(","))); routes.add(r); } return routes; } public List getDirectionsForStop(String stopID, AtomicReference res) { String response = performAPIRequest(QueryType.DETAILS,stopID,res); List routes; try{ routes = parseDirectionsFromResponse(response); res.set(result.OK); } catch (JSONException | IllegalArgumentException e) { e.printStackTrace(); res.set(result.PARSER_ERROR); routes = null; } return routes; } public ArrayList getAllStopsFromGTT(AtomicReference res){ String response = performAPIRequest(QueryType.STOPS_ALL,null,res); if(response==null) return null; ArrayList stopslist; try{ JSONObject responseJSON = new JSONObject(response); JSONArray stops = responseJSON.getJSONArray("stops"); stopslist = new ArrayList<>(stops.length()); for (int i=0;i getAllLinesFromGTT(AtomicReference res){ String resp = performAPIRequest(QueryType.LINES,null,res); if(resp==null) { return null; } ArrayList routes = null; try { JSONArray lines = new JSONArray(resp); routes = new ArrayList<>(lines.length()); for(int i = 0; i getHeadersForRequest(String url) throws UnsupportedEncodingException, NoSuchAlgorithmException { - final Date d = new Date(); + public static Map getDefaultHeaders(){ HashMap param = new HashMap<>(); - param.put("TOKEN",getAccessToken(url,d)); - param.put("TIMESTAMP",String.valueOf(d.getTime())); - param.put("Accept-Encoding","gzip"); + param.put("Host","www.5t.torino.it"); param.put("Connection","Keep-Alive"); - + param.put("Accept-Encoding", "gzip"); return param; - } /** * Create and perform the network request. This method adds parameters and returns the result * @param t type of request to be performed * @param stopID optional parameter, stop ID which you need for passages and branches * @param res result container * @return a String which contains the result of the query, to be parsed */ @Nullable public static String performAPIRequest(QueryType t,@Nullable String stopID, AtomicReference res){ URL u; Map param; try { String address = getURLForOperation(t,stopID); //Log.d(DEBUG_NAME,"The address to query is: "+address); - param = getHeadersForRequest(address); + param = getDefaultHeaders(); u = new URL(address); - } catch (UnsupportedEncodingException | NoSuchAlgorithmException |MalformedURLException e) { + } catch (UnsupportedEncodingException |MalformedURLException e) { e.printStackTrace(); res.set(result.PARSER_ERROR); return null; } String response = networkTools.queryURL(u,res,param); return response; } - /** - * Get the Token needed to access the API - * @param URL the URL of the request - * @return token - * @throws NoSuchAlgorithmException if the system doesn't support MD5 - * @throws UnsupportedEncodingException if we made mistakes in writing utf-8 - */ - private static String getAccessToken(String URL,Date d) throws NoSuchAlgorithmException,UnsupportedEncodingException{ - MessageDigest md = MessageDigest.getInstance("MD5"); - String strippedQuery = URL.replace("http://www.5t.torino.it/proxyws",""); - //return the time in milliseconds - long timeMilli = d.getTime(); - StringBuilder sb = new StringBuilder(); - sb.append(strippedQuery); - sb.append(timeMilli); - sb.append(SECRET_KEY); - String stringToBeHashed = sb.toString(); - //Log.d(DEBUG_NAME,"Hashing string: "+stringToBeHashed); - md.reset(); - byte[] data = md.digest(stringToBeHashed.getBytes("UTF-8")); - sb = new StringBuilder(); - for (byte b : data){ - sb.append(String.format("%02x",b)); - } - String result = sb.toString(); - //Log.d(DEBUG_NAME,"getting token:\n\treduced URL: "+strippedQuery+"\n\ttimestamp: "+timeMilli+"\nTOKEN:"+result.toLowerCase()); - return result.toLowerCase(); - } - /** * Get the right url for the operation you are doing, to be fed into the queryURL method * @param t type of operation * @param stopID stop on which you are working on * @return the Url to go to * @throws UnsupportedEncodingException if it cannot be converted to utf-8 */ public static String getURLForOperation(QueryType t,@Nullable String stopID) throws UnsupportedEncodingException { final StringBuilder sb = new StringBuilder(); - sb.append("http://www.5t.torino.it/proxyws/ws2.1/rest/"); + sb.append("http://www.5t.torino.it/ws2.1/rest/"); if(t!=QueryType.LINES) sb.append("stops/"); switch (t){ case ARRIVALS: sb.append(URLEncoder.encode(stopID,"utf-8")); sb.append("/departures"); break; case DETAILS: sb.append(URLEncoder.encode(stopID,"utf-8")); sb.append("/branches/details"); break; case STOPS_ALL: sb.append("all"); break; case STOPS_VERSION: sb.append("version"); break; case LINES: sb.append("lines/all"); break; } return sb.toString(); } public enum QueryType { ARRIVALS, DETAILS,STOPS_ALL, STOPS_VERSION,LINES } } diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java b/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java index 4377bc3..1152efb 100644 --- a/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIVolleyRequest.java @@ -1,137 +1,131 @@ /* BusTO - Backend components Copyright (C) 2019 Fabio Mazza 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, see . */ package it.reyboz.bustorino.backend; import android.support.annotation.Nullable; import android.util.Log; import com.android.volley.*; import com.android.volley.toolbox.HttpHeaderParser; import org.json.JSONException; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** * Class to handle request with the Volley Library */ public class FiveTAPIVolleyRequest extends Request { private static final String LOG_TAG = "BusTO-FiveTAPIVolleyReq"; private ResponseListener listener; final private String url,stopID; final private AtomicReference resultRef; final private FiveTAPIFetcher fetcher; final private FiveTAPIFetcher.QueryType type; private FiveTAPIVolleyRequest(int method, String url, String stopID, FiveTAPIFetcher.QueryType kind, ResponseListener listener, @Nullable Response.ErrorListener errorListener) { super(method, url, errorListener); this.url = url; this.resultRef = new AtomicReference<>(); this.fetcher = new FiveTAPIFetcher(); this.listener = listener; this.stopID = stopID; this.type = kind; } @Nullable public static FiveTAPIVolleyRequest getNewRequest(FiveTAPIFetcher.QueryType type, String stopID, ResponseListener listener, @Nullable Response.ErrorListener errorListener){ String url; try { url = FiveTAPIFetcher.getURLForOperation(type,stopID); } catch (UnsupportedEncodingException e) { e.printStackTrace(); Log.e(LOG_TAG,"Cannot get an URL for the operation"); return null; } - return new FiveTAPIVolleyRequest(Method.POST,url,stopID,type,listener,errorListener); + return new FiveTAPIVolleyRequest(Method.GET,url,stopID,type,listener,errorListener); } @Override protected Response parseNetworkResponse(NetworkResponse response) { if(response.statusCode != 200) return Response.error(new VolleyError("Response Error Code "+response.statusCode)); final String stringResponse = new String(response.data); List routeList; try{ switch (type){ case ARRIVALS: routeList = fetcher.parseArrivalsServerResponse(stringResponse,resultRef); break; case DETAILS: routeList = fetcher.parseDirectionsFromResponse(stringResponse); break; default: //empty return Response.error(new VolleyError("Invalid query type")); } } catch (JSONException e) { resultRef.set(Fetcher.result.PARSER_ERROR); //e.printStackTrace(); Log.w("FivetVolleyParser","JSON Exception in parsing response of: "+url); return Response.error(new ParseError(response)); } if(resultRef.get()== Fetcher.result.PARSER_ERROR){ return Response.error(new ParseError(response)); } final Palina p = new Palina(stopID); p.setRoutes(routeList); p.sortRoutes(); return Response.success(p, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(Palina p) { listener.onResponse(p,type); } @Override - public Map getHeaders() throws AuthFailureError { - Map headers; - try{ - headers = FiveTAPIFetcher.getHeadersForRequest(url); - } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { - e.printStackTrace(); - throw new AuthFailureError("Cannot get the token for the authorization"); - } - return headers; + public Map getHeaders() { + return FiveTAPIFetcher.getDefaultHeaders(); + } //from https://stackoverflow.com/questions/21867929/android-how-handle-message-error-from-the-server-using-volley @Override protected VolleyError parseNetworkError(VolleyError volleyError){ if(volleyError.networkResponse != null && volleyError.networkResponse.data != null){ volleyError = new NetworkError(volleyError.networkResponse); } return volleyError; } public interface ResponseListener{ void onResponse(Palina result, FiveTAPIFetcher.QueryType type); } //public interface ErrorListener extends Response.ErrorListener{} }