Agregados varios //TODO para revisar

This commit is contained in:
2025-12-04 21:22:05 +01:00
parent e0133d2ca2
commit ec57ac366d
9 changed files with 837 additions and 118 deletions

View File

@@ -72,6 +72,16 @@ Body:
POST /portroyalmanager/secure/stationsobservations/
Base: https://estaciones.api.adif.es
Headers: User-key para estaciones
Body:
{
"stationCodes": ["string"] // Array de códigos de estación (requerido)
}
Ejemplo:
{
"stationCodes": ["60000", "71801"]
}
```
### Circulaciones (Trenes)
@@ -84,16 +94,28 @@ Headers: User-key para circulaciones
Body:
{
"commercialService": "YES|NO|ALL",
"commercialStopType": "YES|NO|ALL",
"destinationStationCode": "string|null",
"originStationCode": "string|null",
"commercialService": "YES|NOT|BOTH", // Estado del servicio comercial
"commercialStopType": "YES|NOT|BOTH", // Tipo de parada comercial
"destinationStationCode": "string|null", // Código estación destino (opcional)
"originStationCode": "string|null", // Código estación origen (opcional)
"page": {
"page": number,
"size": number
"pageNumber": number // Número de página
},
"stationCode": "string|null",
"trafficType": "CERCANIAS|MEDIA_DISTANCIA|LARGA_DISTANCIA|ALL"
"stationCode": "string|null", // Código estación (opcional)
"trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL" // Tipo de tráfico
}
Ejemplo:
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"destinationStationCode": null,
"originStationCode": null,
"page": {
"pageNumber": 0
},
"stationCode": "60000",
"trafficType": "ALL"
}
```
@@ -102,7 +124,8 @@ Body:
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Same as departures
Body: Mismo formato que departures (TrafficCirculationPathRequest)
```
#### Entre estaciones
@@ -110,7 +133,8 @@ Body: Same as departures
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Same as departures
Body: Mismo formato que departures (TrafficCirculationPathRequest)
```
#### Una ruta específica
@@ -121,11 +145,20 @@ Headers: User-key para circulaciones
Body:
{
"allControlPoints": boolean|null,
"commercialNumber": "string|null",
"destinationStationCode": "string|null",
"launchingDate": timestamp|null,
"originStationCode": "string|null"
"allControlPoints": boolean|null, // Todos los puntos de control (opcional)
"commercialNumber": "string|null", // Número comercial del tren (opcional)
"destinationStationCode": "string|null", // Código estación destino (opcional)
"launchingDate": number|null, // Fecha de lanzamiento en timestamp (Long) (opcional)
"originStationCode": "string|null" // Código estación origen (opcional)
}
Ejemplo:
{
"allControlPoints": true,
"commercialNumber": "04138",
"destinationStationCode": "60000",
"launchingDate": 1733356800000,
"originStationCode": "71801"
}
```
@@ -134,7 +167,8 @@ Body:
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
Base: https://circulacion.api.adif.es
Headers: User-key para circulaciones
Body: Same as onepaths
Body: Mismo formato que onepaths (OneOrSeveralPathsRequest)
```
### Composiciones
@@ -215,20 +249,21 @@ Headers: Basic auth + X-CanalMovil headers
### TrafficType (Tipos de tráfico)
- `CERCANIAS` - Trenes de cercanías
- `MEDIA_DISTANCIA` - Media distancia
- `LARGA_DISTANCIA` - Larga distancia
- `AVLDMD` - Alta Velocidad, Larga y Media Distancia
- `OTHERS` - Otros tipos de tráfico
- `TRAVELERS` - Viajeros
- `GOODS` - Mercancías
- `ALL` - Todos los tipos
### State (Estados)
### State (Estados para comercialService y comercialStopType)
- `YES` - Sí
- `NO` - No
- `ALL` - Todos
- `NOT` - No
- `BOTH` - Ambos
### PageInfoDTO
```json
{
"page": 0,
"size": 20
"pageNumber": 0
}
```
@@ -239,9 +274,13 @@ Headers: Basic auth + X-CanalMovil headers
- Las User-keys son diferentes para cada servicio (estaciones vs circulaciones)
- El token de registro `b9034774-c6e4-4663-a1a8-74bf7102651b` está en el código
[CODE] 200
[METHOD] POST
[URL] https://circulacion.api.adif.es/portroyalmanager/secure/circulationpathdetails/onepaths/
[URL] https://circulacion.api.adif.es/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
[URL] https://circulacion.api.adif.es/portroyalmanager/secure/circulationpaths/departures/traffictype/
[URL] https://estaciones.api.adif.es/portroyalmanager/secure/stationsobservations/
## Notas de Implementación
Esta documentación se ha obtenido mediante ingeniería reversa del código decompilado de la aplicación Android de ADIF Elcano.
Clases principales analizadas:
- `com.adif.elcanomovil.serviceNetworking.circulations.model.request.TrafficCirculationPathRequest`
- `com.adif.elcanomovil.serviceNetworking.circulations.model.request.OneOrSeveralPathsRequest`
- `com.adif.elcanomovil.serviceNetworking.stationObservations.model.StationObservationsRequest`
- `com.adif.elcanomovil.serviceNetworking.circulations.model.request.CirculationPathRequest` (interface)
- `com.adif.elcanomovil.serviceNetworking.circulations.model.request.TrafficType` (enum)

View File

@@ -4,10 +4,11 @@ import java.util.List;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
// TODO
@Metadata(d1 = {"\u0000L\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010 \n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\b\u0018\n\u0002\u0010\u000b\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0002\b\u0086\b\u0018\u00002\u00020\u0001Bk\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\b\u0010\u0004\u001a\u0004\u0018\u00010\u0005\u0012\b\u0010\u0006\u001a\u0004\u0018\u00010\u0007\u0012\u000e\u0010\b\u001a\n\u0012\u0004\u0012\u00020\n\u0018\u00010\t\u0012\u000e\u0010\u000b\u001a\n\u0012\u0004\u0012\u00020\f\u0018\u00010\t\u0012\u000e\u0010\r\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\t\u0012\u000e\u0010\u000f\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\t\u0012\b\u0010\u0010\u001a\u0004\u0018\u00010\u0011¢\u0006\u0002\u0010\u0012J\t\u0010 \u001a\u00020\u0003HÆ\u0003J\u000b\u0010!\u001a\u0004\u0018\u00010\u0005HÆ\u0003J\u000b\u0010\"\u001a\u0004\u0018\u00010\u0007HÆ\u0003J\u0011\u0010#\u001a\n\u0012\u0004\u0012\u00020\n\u0018\u00010\tHÆ\u0003J\u0011\u0010$\u001a\n\u0012\u0004\u0012\u00020\f\u0018\u00010\tHÆ\u0003J\u0011\u0010%\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\tHÆ\u0003J\u0011\u0010&\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\tHÆ\u0003J\u000b\u0010'\u001a\u0004\u0018\u00010\u0011HÆ\u0003J\u007f\u0010(\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\n\b\u0002\u0010\u0004\u001a\u0004\u0018\u00010\u00052\n\b\u0002\u0010\u0006\u001a\u0004\u0018\u00010\u00072\u0010\b\u0002\u0010\b\u001a\n\u0012\u0004\u0012\u00020\n\u0018\u00010\t2\u0010\b\u0002\u0010\u000b\u001a\n\u0012\u0004\u0012\u00020\f\u0018\u00010\t2\u0010\b\u0002\u0010\r\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\t2\u0010\b\u0002\u0010\u000f\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\t2\n\b\u0002\u0010\u0010\u001a\u0004\u0018\u00010\u0011HÆ\u0001J\u0013\u0010)\u001a\u00020*2\b\u0010+\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010,\u001a\u00020-HÖ\u0001J\t\u0010.\u001a\u00020\u0003HÖ\u0001R\u0013\u0010\u0010\u001a\u0004\u0018\u00010\u0011¢\u0006\b\n\u0000\u001a\u0004\b\u0013\u0010\u0014R\u0013\u0010\u0006\u001a\u0004\u0018\u00010\u0007¢\u0006\b\n\u0000\u001a\u0004\b\u0015\u0010\u0016R\u0019\u0010\u000f\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\\u0006\b\n\u0000\u001a\u0004\b\u0017\u0010\u0018R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0019\u0010\u001aR\u0019\u0010\r\u001a\n\u0012\u0004\u0012\u00020\u000e\u0018\u00010\\u0006\b\n\u0000\u001a\u0004\b\u001b\u0010\u0018R\u0013\u0010\u0004\u001a\u0004\u0018\u00010\u0005¢\u0006\b\n\u0000\u001a\u0004\b\u001c\u0010\u001dR\u0019\u0010\b\u001a\n\u0012\u0004\u0012\u00020\n\u0018\u00010\\u0006\b\n\u0000\u001a\u0004\b\u001e\u0010\u0018R\u0019\u0010\u000b\u001a\n\u0012\u0004\u0012\u00020\f\u0018\u00010\\u0006\b\n\u0000\u001a\u0004\b\u001f\u0010\u0018¨\u0006/"}, d2 = {"Lcom/adif/elcanomovil/domain/entities/station/RequestedStationInfo;", "", "stationCode", "", "stationInfo", "Lcom/adif/elcanomovil/domain/entities/station/StationInfo;", "extendedStationInfo", "Lcom/adif/elcanomovil/domain/entities/station/ExtendedStationInfo;", "stationServices", "", "Lcom/adif/elcanomovil/domain/entities/station/StationServices;", "stationTransportServices", "Lcom/adif/elcanomovil/domain/entities/station/StationTransportServices;", "stationCommercialServices", "Lcom/adif/elcanomovil/domain/entities/station/StationCommercialServices;", "stationActivities", "banner", "Lcom/adif/elcanomovil/domain/entities/station/Banner;", "(Ljava/lang/String;Lcom/adif/elcanomovil/domain/entities/station/StationInfo;Lcom/adif/elcanomovil/domain/entities/station/ExtendedStationInfo;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/adif/elcanomovil/domain/entities/station/Banner;)V", "getBanner", "()Lcom/adif/elcanomovil/domain/entities/station/Banner;", "getExtendedStationInfo", "()Lcom/adif/elcanomovil/domain/entities/station/ExtendedStationInfo;", "getStationActivities", "()Ljava/util/List;", "getStationCode", "()Ljava/lang/String;", "getStationCommercialServices", "getStationInfo", "()Lcom/adif/elcanomovil/domain/entities/station/StationInfo;", "getStationServices", "getStationTransportServices", "component1", "component2", "component3", "component4", "component5", "component6", "component7", "component8", "copy", "equals", "", "other", "hashCode", "", "toString", "domain_proNon_corporateRelease"}, k = 1, mv = {1, 9, 0}, xi = 48)
/* loaded from: classes.dex */
public final /* data */ class RequestedStationInfo {
private final Banner banner;
private final Banner banner;StationService
private final ExtendedStationInfo extendedStationInfo;
private final List<StationCommercialServices> stationActivities;
private final String stationCode;

View File

@@ -3,7 +3,7 @@ package com.adif.elcanomovil.serviceNetworking;
import com.adif.elcanomovil.commonNavGraph.arguments.NavArguments;
import com.google.firebase.analytics.FirebaseAnalytics;
import kotlin.Metadata;
//TODO
@Metadata(d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u000b\\u0002\u0018\u00002\u00020\u0001:\t\u0003\u0004\u0005\u0006\u0007\b\t\n\u000bB\u0007\b\u0002¢\u0006\u0002\u0010\u0002¨\u0006\f"}, d2 = {"Lcom/adif/elcanomovil/serviceNetworking/ServicePaths;", "", "()V", "AvisaLoginService", "AvisaStationService", "CirculationService", "CompositionService", "Headers", "IncidenceService", "StationObservationsService", "StationService", "SubscriptionsService", "service-networking_proNon_corporateRelease"}, k = 1, mv = {1, 9, 0}, xi = 48)
/* loaded from: classes.dex */
public final class ServicePaths {

View File

@@ -1,36 +1,77 @@
/**
* Capture REQUEST BODY using writeTo() method
* Capture REQUEST BODY by hooking MoshiRequestBodyConverter
*/
console.log("\n[*] Capturing REQUEST Bodies\n");
console.log("\n[*] Capturing REQUEST Bodies via MoshiRequestBodyConverter\n");
Java.perform(function() {
// Hook MoshiRequestBodyConverter.convert() directly
try {
var MoshiRequestBodyConverter = Java.use("retrofit2.converter.moshi.MoshiRequestBodyConverter");
console.log("[+] Found MoshiRequestBodyConverter");
var convertOriginal = MoshiRequestBodyConverter.convert.overload('java.lang.Object');
convertOriginal.implementation = function(obj) {
// BEFORE calling original, serialize the object ourselves to capture it
try {
// Get the adapter field to serialize the object
var adapterField = this.getClass().getDeclaredField("adapter");
adapterField.setAccessible(true);
var adapter = adapterField.get(this);
// Create our own buffer and writer to capture the JSON
var Buffer = Java.use("r3.f");
var tempBuffer = Buffer.$new();
// Create JsonWriter with buffer
var JsonWriter = Java.use("Z2.t");
var JsonWriterConstructor = JsonWriter.class.getDeclaredConstructor([Java.use("r3.i").class]);
JsonWriterConstructor.setAccessible(true);
var tempWriter = JsonWriterConstructor.newInstance([tempBuffer]);
// Serialize to our buffer
adapter.toJson(tempWriter, obj);
tempWriter.close();
// Read the JSON
var jsonContent = tempBuffer.B0(); // readUtf8()
console.log("\n" + "=".repeat(80));
console.log("[CAPTURED REQUEST BODY]");
if (jsonContent && jsonContent.length > 0) {
if (jsonContent.length > 3000) {
console.log(jsonContent.substring(0, 3000));
console.log("\n... (truncated, total: " + jsonContent.length + " chars)");
} else {
console.log(jsonContent);
}
} else {
console.log("(empty)");
}
console.log("=".repeat(80) + "\n");
} catch (e) {
console.log("[CAPTURE ERROR] " + e);
}
// Call original to return the actual RequestBody
return convertOriginal.call(this, obj);
};
console.log("[*] MoshiRequestBodyConverter hook installed!\n");
} catch (e) {
console.log("[-] Failed to hook MoshiRequestBodyConverter: " + e);
}
// Also hook the Auth interceptor to show URLs
try {
var AuthHeaderInterceptor = Java.use("com.adif.elcanomovil.serviceNetworking.interceptors.AuthHeaderInterceptor");
console.log("[+] Found AuthHeaderInterceptor");
// Try to find Buffer class
var Buffer = null;
var bufferNames = ["r.f", "r3.f", "okio.Buffer", "r3.Buffer"];
for (var i = 0; i < bufferNames.length; i++) {
try {
Buffer = Java.use(bufferNames[i]);
console.log("[+] Found Buffer class: " + bufferNames[i]);
break;
} catch (e) {
// Try next
}
}
if (!Buffer) {
console.log("[-] Could not find Buffer class, trying without pre-loading");
}
AuthHeaderInterceptor.intercept.implementation = function(chain) {
console.log("\n" + "=".repeat(80));
console.log("[HTTP REQUEST]");
try {
// Cast chain
var ChainClass = Java.use("j3.g");
@@ -46,87 +87,26 @@ Java.perform(function() {
var urlField = request.getClass().getDeclaredField("a");
urlField.setAccessible(true);
var urlObj = urlField.get(request);
console.log("[URL] " + urlObj.toString());
// Get method
var methodField = request.getClass().getDeclaredField("b");
methodField.setAccessible(true);
var method = methodField.get(request);
console.log("[METHOD] " + method);
// Get request body
var bodyField = request.getClass().getDeclaredField("d");
bodyField.setAccessible(true);
var reqBody = bodyField.get(request);
if (reqBody) {
try {
// If Buffer wasn't found, try to load it now
if (!Buffer) {
var bufferNames = ["r.f", "r3.f", "okio.Buffer", "r3.Buffer"];
for (var i = 0; i < bufferNames.length; i++) {
try {
Buffer = Java.use(bufferNames[i]);
break;
} catch (e) {}
}
}
if (Buffer) {
// Create a temporary buffer
var buffer = Buffer.$new();
// Try to cast buffer to BufferedSink if needed
try {
var BufferedSink = Java.use("r3.i");
var sink = Java.cast(buffer, BufferedSink);
// Call writeTo passing the sink
reqBody.writeTo(sink);
} catch (e) {
// If cast fails, try direct call
reqBody.writeTo(buffer);
}
// Read the content as UTF-8 string
var bodyContent = buffer.B0(); // readUtf8()
console.log("\n[REQUEST BODY]");
if (bodyContent && bodyContent.length > 0) {
if (bodyContent.length > 2000) {
console.log(bodyContent.substring(0, 2000));
console.log("\n... (truncated, total: " + bodyContent.length + " chars)");
} else {
console.log(bodyContent);
}
} else {
console.log("(empty)");
}
} else {
console.log("\n[REQUEST BODY] Could not load Buffer class");
console.log("\n[REQUEST] " + method + " " + urlObj.toString());
}
} catch (e) {
console.log("[REQUEST BODY ERROR] " + e);
console.log("[URL CAPTURE ERROR] " + e);
}
} else {
console.log("[REQUEST BODY] null");
}
}
} catch (e) {
console.log("[ERROR] " + e);
}
console.log("=".repeat(80) + "\n");
// Call original
return this.intercept(chain);
};
console.log("[*] Hook installed!\n");
console.log("[*] Interceptor hook installed!\n");
} catch (e) {
console.log("[-] Failed: " + e);
console.log("[-] Failed to hook AuthHeaderInterceptor: " + e);
}
});

View File

@@ -0,0 +1,130 @@
/**
* Improved REQUEST BODY Capture
* Using correct method names discovered through inspection
*/
console.log("\n[*] Improved Request Body Capture\n");
Java.perform(function() {
try {
var AuthHeaderInterceptor = Java.use("com.adif.elcanomovil.serviceNetworking.interceptors.AuthHeaderInterceptor");
console.log("[+] Found AuthHeaderInterceptor");
AuthHeaderInterceptor.intercept.implementation = function(chain) {
console.log("\n" + "=".repeat(80));
console.log("[HTTP REQUEST]");
try {
// Cast chain
var ChainClass = Java.use("j3.g");
var chainObj = Java.cast(chain, ChainClass);
// Get request
var requestField = chainObj.getClass().getDeclaredField("e");
requestField.setAccessible(true);
var request = requestField.get(chainObj);
if (request) {
// Get URL
var urlField = request.getClass().getDeclaredField("a");
urlField.setAccessible(true);
var urlObj = urlField.get(request);
console.log("[URL] " + urlObj.toString());
// Get method
var methodField = request.getClass().getDeclaredField("b");
methodField.setAccessible(true);
var method = methodField.get(request);
console.log("[METHOD] " + method);
// Get request headers
try {
var headersField = request.getClass().getDeclaredField("c");
headersField.setAccessible(true);
var headers = headersField.get(request);
if (headers) {
console.log("\n[REQUEST HEADERS]");
var size = headers.size();
for (var i = 0; i < size; i++) {
var name = headers.c(i);
var value = headers.f(i);
console.log(" " + name + ": " + value);
}
}
} catch (e) {
console.log("[HEADERS ERROR] " + e);
}
// Get request body
var bodyField = request.getClass().getDeclaredField("d");
bodyField.setAccessible(true);
var reqBody = bodyField.get(request);
if (reqBody) {
try {
// Load Buffer class - we know it's r3.f from inspection
var Buffer = Java.use("r3.f");
var buffer = Buffer.$new();
// Call writeTo with the buffer (buffer implements BufferedSink)
reqBody.writeTo(buffer);
// Try to read using readUtf8
try {
var bodyContent = buffer.B0(); // readUtf8()
console.log("\n[REQUEST BODY]");
if (bodyContent && bodyContent.length > 0) {
if (bodyContent.length > 3000) {
console.log(bodyContent.substring(0, 3000));
console.log("\n... (truncated, total: " + bodyContent.length + " chars)");
} else {
console.log(bodyContent);
}
} else {
console.log("(empty)");
}
} catch (e) {
// If B0() doesn't work, try other common method names
console.log("[READ ERROR] " + e);
console.log("[DEBUG] Trying alternative methods...");
try {
// Try snapshot().utf8()
var snapshot = buffer.t0(); // snapshot()
if (snapshot) {
var bodyContent = snapshot.Y(); // utf8()
console.log("\n[REQUEST BODY]");
console.log(bodyContent);
}
} catch (e2) {
console.log("[ALT METHOD ERROR] " + e2);
}
}
} catch (e) {
console.log("[REQUEST BODY ERROR] " + e);
}
} else {
console.log("[REQUEST BODY] null");
}
}
} catch (e) {
console.log("[ERROR] " + e);
}
console.log("=".repeat(80) + "\n");
// Call original
return this.intercept(chain);
};
console.log("[*] Hook installed!\n");
} catch (e) {
console.log("[-] Failed: " + e);
}
});

View File

@@ -0,0 +1,68 @@
/**
* Intercept at OkHttp level to capture request bodies
*/
console.log("\n[*] OkHttp Request Interceptor\n");
Java.perform(function() {
// Hook the RealCall.execute method which actually sends the request
try {
var RealCall = Java.use("i3.j"); // OkHttp's RealCall
console.log("[+] Found RealCall");
RealCall.g.implementation = function(chain) {
console.log("\n" + "=".repeat(80));
console.log("[HTTP REQUEST INTERCEPTED]");
try {
// Get the request from chain
var request = chain.b();
if (request) {
console.log("[URL] " + request.g().toString());
console.log("[METHOD] " + request.f());
// Get the body
var body = request.d();
if (body) {
try {
var Buffer = Java.use("r3.f");
var buffer = Buffer.$new();
// Write body to buffer
body.writeTo(buffer);
// Read as string
var bodyStr = buffer.B0();
console.log("\n[REQUEST BODY]");
if (bodyStr && bodyStr.length > 0) {
console.log(bodyStr);
} else {
console.log("(empty)");
}
} catch (e) {
console.log("[BODY ERROR] " + e);
}
} else {
console.log("[BODY] null");
}
}
} catch (e) {
console.log("[ERROR] " + e);
}
console.log("=".repeat(80) + "\n");
// Call original
return this.g(chain);
};
console.log("[*] Hook installed!\n");
} catch (e) {
console.log("[-] Failed to hook RealCall: " + e);
}
});

View File

@@ -0,0 +1,118 @@
/**
* Request Body Capture using Reflection
* Automatically finds the correct method names
*/
console.log("\n[*] Request Body Capture (Reflection-based)\n");
Java.perform(function() {
try {
var AuthHeaderInterceptor = Java.use("com.adif.elcanomovil.serviceNetworking.interceptors.AuthHeaderInterceptor");
console.log("[+] Found AuthHeaderInterceptor");
AuthHeaderInterceptor.intercept.implementation = function(chain) {
console.log("\n" + "=".repeat(80));
console.log("[HTTP REQUEST]");
try {
// Cast chain
var ChainClass = Java.use("j3.g");
var chainObj = Java.cast(chain, ChainClass);
// Get request
var requestField = chainObj.getClass().getDeclaredField("e");
requestField.setAccessible(true);
var request = requestField.get(chainObj);
if (request) {
// Get URL
var urlField = request.getClass().getDeclaredField("a");
urlField.setAccessible(true);
var urlObj = urlField.get(request);
console.log("[URL] " + urlObj.toString());
// Get method
var methodField = request.getClass().getDeclaredField("b");
methodField.setAccessible(true);
var method = methodField.get(request);
console.log("[METHOD] " + method);
// Get request body
var bodyField = request.getClass().getDeclaredField("d");
bodyField.setAccessible(true);
var reqBody = bodyField.get(request);
if (reqBody) {
try {
// Load Buffer class
var Buffer = Java.use("r3.f");
var buffer = Buffer.$new();
// Call writeTo with the buffer
reqBody.writeTo(buffer);
// Use reflection to find readUtf8() method
var methods = buffer.getClass().getMethods();
var readUtf8Method = null;
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
var methodName = method.getName();
var returnType = method.getReturnType().getName();
var paramCount = method.getParameterTypes().length;
// Look for a method that returns String and has no parameters
if (returnType === "java.lang.String" && paramCount === 0) {
// This is likely readUtf8()
readUtf8Method = method;
console.log("[DEBUG] Found string method: " + methodName + "()");
break;
}
}
if (readUtf8Method) {
readUtf8Method.setAccessible(true);
var bodyContent = readUtf8Method.invoke(buffer);
console.log("\n[REQUEST BODY]");
if (bodyContent && bodyContent.length > 0) {
if (bodyContent.length > 3000) {
console.log(bodyContent.substring(0, 3000));
console.log("\n... (truncated, total: " + bodyContent.length + " chars)");
} else {
console.log(bodyContent);
}
} else {
console.log("(empty)");
}
} else {
console.log("[REQUEST BODY] Could not find readUtf8() method");
}
} catch (e) {
console.log("[REQUEST BODY ERROR] " + e);
console.log("[STACK] " + e.stack);
}
} else {
console.log("[REQUEST BODY] null");
}
}
} catch (e) {
console.log("[ERROR] " + e);
console.log("[STACK] " + e.stack);
}
console.log("=".repeat(80) + "\n");
// Call original
return this.intercept(chain);
};
console.log("[*] Hook installed!\n");
} catch (e) {
console.log("[-] Failed: " + e);
}
});

203
test_corrected_api.py Normal file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""
Script para probar los endpoints con los valores correctos
obtenidos del código decompilado
"""
import requests
import json
from datetime import datetime
# Headers correctos
HEADERS_CIRCULATION = {
"Content-Type": "application/json;charset=utf-8",
"User-key": "f4ce9fbfa9d721e39b8984805901b5df"
}
HEADERS_STATIONS = {
"Content-Type": "application/json;charset=utf-8",
"User-key": "0d021447a2fd2ac64553674d5a0c1a6f"
}
# URLs base
BASE_CIRCULATION = "https://circulacion.api.adif.es"
BASE_STATIONS = "https://estaciones.api.adif.es"
def test_endpoint(name, method, url, headers, data=None):
"""Probar un endpoint y mostrar resultado"""
print(f"\n{'='*70}")
print(f"TEST: {name}")
print(f"{'='*70}")
print(f"URL: {url}")
if data:
print(f"Body:\n{json.dumps(data, indent=2)}")
try:
if method == "GET":
response = requests.get(url, headers=headers, timeout=10)
elif method == "POST":
response = requests.post(url, headers=headers, json=data, timeout=10)
else:
print(f"❌ Método {method} no soportado")
return False
print(f"\nStatus: {response.status_code}")
if response.status_code == 200:
print("✅ SUCCESS")
result = response.json()
print(f"\nResponse Preview (primeros 500 chars):")
print(json.dumps(result, indent=2, ensure_ascii=False)[:500])
if len(json.dumps(result)) > 500:
print("...")
return True
else:
print(f"❌ FAILED")
print(f"Response: {response.text[:300]}")
return False
except Exception as e:
print(f"❌ EXCEPTION: {str(e)}")
return False
def main():
print("=" * 70)
print("PRUEBAS CON VALORES CORRECTOS DEL CÓDIGO DECOMPILADO")
print("=" * 70)
results = {}
# Test 1: Salidas con State correcto (BOTH en lugar de ALL)
print("\n\n### TEST 1: Departures con State=BOTH ###")
results['departures_both'] = test_endpoint(
"Salidas - Madrid Atocha (State=BOTH, TrafficType=ALL)",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "BOTH", # Correcto: BOTH (no ALL)
"commercialStopType": "BOTH", # Correcto: BOTH (no ALL)
"destinationStationCode": None,
"originStationCode": None,
"page": {
"pageNumber": 0 # Correcto: pageNumber (no page+size)
},
"stationCode": "10200", # Madrid Atocha
"trafficType": "ALL" # Correcto: ALL existe en TrafficType
}
)
# Test 2: Salidas con State YES y NOT
print("\n\n### TEST 2: Departures con State=YES ###")
results['departures_yes'] = test_endpoint(
"Salidas - Madrid Atocha (State=YES)",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "YES", # Correcto: YES
"commercialStopType": "NOT", # Correcto: NOT (no NO)
"destinationStationCode": None,
"originStationCode": None,
"page": {
"pageNumber": 0
},
"stationCode": "10200",
"trafficType": "CERCANIAS"
}
)
# Test 3: Prueba con TrafficType AVLDMD (correcto)
print("\n\n### TEST 3: Departures con TrafficType=AVLDMD ###")
results['departures_avldmd'] = test_endpoint(
"Salidas - Madrid Atocha (TrafficType=AVLDMD)",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"destinationStationCode": None,
"originStationCode": None,
"page": {
"pageNumber": 0
},
"stationCode": "10200",
"trafficType": "AVLDMD" # Correcto: AVLDMD (no LARGA_DISTANCIA)
}
)
# Test 4: Station Observations con stationCodes (array)
print("\n\n### TEST 4: Station Observations (stationCodes array) ###")
results['station_observations'] = test_endpoint(
"Observaciones de Estación (array)",
"POST",
f"{BASE_STATIONS}/portroyalmanager/secure/stationsobservations/",
HEADERS_STATIONS,
{
"stationCodes": ["10200", "10302"] # Correcto: stationCodes (array, no stationCode)
}
)
# Test 5: OneOrSeveralPaths
print("\n\n### TEST 5: OneOrSeveralPaths ###")
results['onepaths'] = test_endpoint(
"Detalles de Ruta Específica",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpathdetails/onepaths/",
HEADERS_CIRCULATION,
{
"allControlPoints": True,
"commercialNumber": None,
"destinationStationCode": "71801", # Barcelona Sants
"launchingDate": None,
"originStationCode": "10200" # Madrid Atocha
}
)
# Test 6: Between Stations
print("\n\n### TEST 6: Between Stations ###")
results['between_stations'] = test_endpoint(
"Entre Estaciones (Madrid - Barcelona)",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"destinationStationCode": "71801", # Barcelona Sants
"originStationCode": "10200", # Madrid Atocha
"page": {
"pageNumber": 0
},
"stationCode": None,
"trafficType": "ALL"
}
)
# Resumen
print("\n\n" + "="*70)
print("RESUMEN DE PRUEBAS")
print("="*70)
total = len(results)
passed = sum(1 for v in results.values() if v)
failed = total - passed
for test_name, result in results.items():
status = "✅ PASS" if result else "❌ FAIL"
print(f"{status} - {test_name}")
print(f"\nTotal: {total} | Pasadas: {passed} | Fallidas: {failed}")
if passed == total:
print("\n🎉 ¡Todas las pruebas pasaron! La documentación es correcta.")
else:
print(f"\n⚠️ {failed} prueba(s) fallaron. Revisar los errores arriba.")
if __name__ == "__main__":
main()

180
test_corrected_api_v2.py Normal file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""
Script para probar los endpoints OMITIENDO campos null
(en lugar de enviarlos explícitamente como null)
"""
import requests
import json
# Headers correctos
HEADERS_CIRCULATION = {
"Content-Type": "application/json;charset=utf-8",
"User-key": "f4ce9fbfa9d721e39b8984805901b5df"
}
HEADERS_STATIONS = {
"Content-Type": "application/json;charset=utf-8",
"User-key": "0d021447a2fd2ac64553674d5a0c1a6f"
}
# URLs base
BASE_CIRCULATION = "https://circulacion.api.adif.es"
BASE_STATIONS = "https://estaciones.api.adif.es"
def test_endpoint(name, method, url, headers, data=None):
"""Probar un endpoint y mostrar resultado"""
print(f"\n{'='*70}")
print(f"TEST: {name}")
print(f"{'='*70}")
print(f"URL: {url}")
if data:
print(f"Body:\n{json.dumps(data, indent=2)}")
try:
if method == "GET":
response = requests.get(url, headers=headers, timeout=10)
elif method == "POST":
response = requests.post(url, headers=headers, json=data, timeout=10)
else:
print(f"❌ Método {method} no soportado")
return False
print(f"\nStatus: {response.status_code}")
if response.status_code == 200:
print("✅ SUCCESS")
result = response.json()
print(f"\nResponse Preview (primeros 1000 chars):")
resp_str = json.dumps(result, indent=2, ensure_ascii=False)
print(resp_str[:1000])
if len(resp_str) > 1000:
print("...")
return True
else:
print(f"❌ FAILED")
print(f"Response: {response.text[:300]}")
return False
except Exception as e:
print(f"❌ EXCEPTION: {str(e)}")
return False
def main():
print("=" * 70)
print("PRUEBAS OMITIENDO CAMPOS NULL")
print("=" * 70)
results = {}
# Test 1: Salidas - SOLO campos requeridos
print("\n\n### TEST 1: Departures - SOLO campos necesarios ###")
results['departures_minimal'] = test_endpoint(
"Salidas - Madrid Atocha (campos mínimos)",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/departures/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"page": {
"pageNumber": 0
},
"stationCode": "10200",
"trafficType": "ALL"
# Omitiendo destinationStationCode, originStationCode que son null
}
)
# Test 2: Station Observations
print("\n\n### TEST 2: Station Observations ###")
results['station_observations'] = test_endpoint(
"Observaciones de Estación",
"POST",
f"{BASE_STATIONS}/portroyalmanager/secure/stationsobservations/",
HEADERS_STATIONS,
{
"stationCodes": ["10200"]
}
)
# Test 3: OneOrSeveralPaths - solo campos necesarios
print("\n\n### TEST 3: OneOrSeveralPaths (campos mínimos) ###")
results['onepaths_minimal'] = test_endpoint(
"Detalles de Ruta - solo estaciones",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpathdetails/onepaths/",
HEADERS_CIRCULATION,
{
"destinationStationCode": "71801",
"originStationCode": "10200"
# Omitiendo allControlPoints, commercialNumber, launchingDate
}
)
# Test 4: Between Stations
print("\n\n### TEST 4: Between Stations (campos mínimos) ###")
results['between_stations'] = test_endpoint(
"Entre Estaciones (Madrid - Barcelona)",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/betweenstations/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"destinationStationCode": "71801",
"originStationCode": "10200",
"page": {
"pageNumber": 0
},
"trafficType": "ALL"
# Omitiendo stationCode que es null
}
)
# Test 5: Arrivals
print("\n\n### TEST 5: Arrivals ###")
results['arrivals'] = test_endpoint(
"Llegadas - Madrid Atocha",
"POST",
f"{BASE_CIRCULATION}/portroyalmanager/secure/circulationpaths/arrivals/traffictype/",
HEADERS_CIRCULATION,
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"page": {
"pageNumber": 0
},
"stationCode": "10200",
"trafficType": "ALL"
}
)
# Resumen
print("\n\n" + "="*70)
print("RESUMEN DE PRUEBAS")
print("="*70)
total = len(results)
passed = sum(1 for v in results.values() if v)
failed = total - passed
for test_name, result in results.items():
status = "✅ PASS" if result else "❌ FAIL"
print(f"{status} - {test_name}")
print(f"\nTotal: {total} | Pasadas: {passed} | Fallidas: {failed}")
if passed == total:
print("\n🎉 ¡Todas las pruebas pasaron!")
elif passed > 0:
print(f"\n{passed} prueba(s) funcionaron correctamente")
else:
print(f"\n⚠️ Todas las pruebas fallaron")
if __name__ == "__main__":
main()