use std::{env, fs, path::PathBuf}; const JAVA_FILES: &[&str] = &[ "src/sys/android/SmsAlarmReceiver.java", "src/sys/android/SmsBootReceiver.java", // D2: ContentObserver on content://sms, so incoming messages push // instead of being polled. "src/sys/android/SmsInboxObserver.java", // E3: AndroidKeyStore envelope encryption for pending schedules. "src/sys/android/SmsScheduleCrypto.java", // E7: send + delivery result receiver. "src/sys/android/SmsSentReceiver.java", ]; fn main() { let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); if target_os == "android" { for f in JAVA_FILES { println!("cargo:rerun-if-changed={f}"); } println!("cargo:rerun-if-env-changed=ROBIUS_SMS_BOOT_LIB"); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let android_jar_path = android_build::android_jar(None).expect("Failed to find android.jar"); let mut java = android_build::JavaBuild::new(); java.class_path(android_jar_path.clone()).classes_out_dir(out_dir.clone()); for rel in JAVA_FILES { let src_path = manifest_dir.join(rel); let file_name = src_path.file_name().unwrap().to_str().unwrap(); if file_name == "SmsBootReceiver.java" { if let Ok(lib_name) = env::var("ROBIUS_SMS_BOOT_LIB") { // E4: validate before interpolating into generated // Java. // // lib_name goes straight into a Java string literal // that is then compiled by javac, dexed, and loaded // at runtime with the app's full permissions. A value // containing a quote closes the literal and injects // arbitrary Java -- e.g. // x"); Runtime.getRuntime().exec("... // would run on the device at boot. A build-time // environment variable is not a trusted input: it // comes from CI config, a Makefile, or a shell the // developer did not write. // // System.loadLibrary takes a bare library name, so // the legitimate character set is small. if lib_name.is_empty() || !lib_name .chars() .all(|c| c.is_ascii_alphanumeric() || c == '_') { panic!( "ROBIUS_SMS_BOOT_LIB must be a bare library name \ matching [A-Za-z0-9_]+ (got {lib_name:?}). It is \ interpolated into generated Java source, so any \ other character is a code-injection vector." ); } let source = fs::read_to_string(&src_path).unwrap(); let needle = " private static native void rustRestoreSchedules(Context context);"; let replacement = format!( " private static native void rustRestoreSchedules(Context context);\n\n\ static {{\n\ try {{\n\ System.loadLibrary(\"{lib_name}\");\n\ }} catch (UnsatisfiedLinkError ignored) {{}}\n\ }}" ); let patched = source.replace(needle, &replacement); let patched_path = out_dir.join(file_name); fs::write(&patched_path, patched).unwrap(); java.file(patched_path); continue; } } java.file(src_path); } assert!( java.compile() .expect("failed to acquire exit status for javac invocation") .success(), "javac invocation failed" ); let d8_jar_path = android_build::android_d8_jar(None).expect("Failed to find d8.jar"); let class_files = [ out_dir.join("robius").join("sms").join("SmsAlarmReceiver.class"), out_dir.join("robius").join("sms").join("SmsBootReceiver.class"), out_dir.join("robius").join("sms").join("SmsInboxObserver.class"), out_dir.join("robius").join("sms").join("SmsScheduleCrypto.class"), out_dir.join("robius").join("sms").join("SmsSentReceiver.class"), ]; let mut run = android_build::JavaRun::new(); run.class_path(d8_jar_path) .main_class("com.android.tools.r8.D8") .arg("--classpath") .arg(android_jar_path) .arg("--output") .arg(&out_dir); for class_file in class_files { run.arg(class_file); } assert!( run.run() .expect("failed to acquire exit status for java d8.jar invocation") .success(), "java d8.jar invocation failed" ); } }