Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c976064b authored by Mohammed Althaf T's avatar Mohammed Althaf T 😊
Browse files

Merge branch '1635-q-json_parser' into 'v1-q'

Update: add json validator

See merge request !168
parents 50df9ebc 80d7bcea
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import androidx.preference.PreferenceManager;
import org.json.JSONException;
import org.lineageos.updater.download.DownloadClient;
import org.lineageos.updater.misc.Constants;
import org.lineageos.updater.misc.JsonValidator;
import org.lineageos.updater.misc.Utils;

import java.io.File;
@@ -67,6 +68,13 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
            Utils.cleanupDownloadsDir(context);
        }

        final File json = Utils.getCachedUpdateList(context);
        boolean isValidatedJsonFile = JsonValidator.validateJsonFile(json);
        if (!isValidatedJsonFile && json.exists()) {
            Log.i(TAG, "Removing cached json file due validation failure");
            json.delete();
        }

        final SharedPreferences preferences =
                PreferenceManager.getDefaultSharedPreferences(context);

@@ -85,7 +93,6 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
            return;
        }

        final File json = Utils.getCachedUpdateList(context);
        final File jsonNew = new File(json.getAbsolutePath() + UUID.randomUUID());
        String url = Utils.getServerURL(context);
        DownloadClient.DownloadCallback callback = new DownloadClient.DownloadCallback() {
@@ -103,6 +110,12 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
            @Override
            public void onSuccess(File destination) {
                try {
                    boolean isValidated = JsonValidator.validateJsonFile(jsonNew);
                    if (!isValidated) {
                        Log.i(TAG, "Could not parse list, scheduling new check");
                        scheduleUpdatesCheck(context);
                        return;
                    }
                    if (json.exists() && Utils.checkForNewUpdates(jsonNew)) {
                        showNotification(context);
                        updateRepeatingUpdatesCheck(context);
+164 −0
Original line number Diff line number Diff line
package org.lineageos.updater.misc;

import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class JsonValidator {

    private static final String TAG = "JsonValidator" ;
    private static final String[] jsonMainFields = {
            "error",
            "id",
            "response"
    };
    private static final String[] requiredFields = {
            "api_level",
            "url",
            "timestamp",
            "md5sum",
            "channel",
            "filename",
            "romtype",
            "version",
            "display_version",
            "android_version",
            "id"
    };
    private static final String[] optionalFields = {
            "changes",
            "pre_version"
    };

    public static boolean validateJsonFile(File jsonFile) {
        try {
            if (!jsonFile.exists()) {
                Log.i(TAG, "Unable to locate json file");
                return false;
            }

            // Read the JSON data from the file
            BufferedReader reader = new BufferedReader(new FileReader(jsonFile));
            StringBuilder jsonString = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                jsonString.append(line);
            }
            reader.close();

            List<String> missingRequiredFields = new ArrayList<>();
            JSONObject jsonObject = new JSONObject(jsonString.toString());
            for (String field : jsonMainFields) {
                if (!isMainFieldValid(jsonObject, field)) {
                    missingRequiredFields.add(field);
                }
            }

            if (!missingRequiredFields.isEmpty()) {
                    Log.i(TAG, "Missing or invalid required field in response object: "
                            + missingRequiredFields);
                return false;
            }
        } catch (IOException | JSONException e) {
            Log.i(TAG, "Unable to parse the json file:" + e);
        }
        return false;
    }

    public static boolean validateResponseObject(JSONObject responseObject) {
        List<String> missingRequiredFields = new ArrayList<>();
        List<String> missingOptionalFields = new ArrayList<>();

        for (String field : requiredFields) {
            if (!isObjectFieldValid(responseObject, field)) {
                missingRequiredFields.add(field);
            }
        }

        for (String field : optionalFields) {
            if (!isObjectFieldValid(responseObject, field)) {
                missingOptionalFields.add(field);
            }
        }

        String filename = responseObject.optString("filename", "");
        if (!missingRequiredFields.isEmpty()) {
            if (!filename.isEmpty()) {
                Log.i(TAG, "Missing or invalid required field in response object for "
                        + filename + ": " + missingRequiredFields);
            }
            return false;
        } else if (!missingOptionalFields.isEmpty() && !filename.isEmpty()) {
            Log.i(TAG, "Missing or invalid optional field in response object for "
                    + filename + ": " + missingOptionalFields);
        }

        return true; // All required fields are present and valid
    }

    private static boolean isMainFieldValid(JSONObject jsonObject, String field) {
        if (!jsonObject.has(field) || jsonObject.isNull(field)) {
            return false;
        }

        String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist

        switch (field) {
            case "id":
            case "error":
                // Allow null, as it's a valid value
                return true;
            case "response":
                // Check if it's an array with a length >= 0
                return jsonObject.optJSONArray(field) != null
                        && jsonObject.optJSONArray(field).length() >= 0;
            default:
                return !value.isEmpty();
        }
    }

    private static boolean isObjectFieldValid(JSONObject jsonObject, String field) {
        if (!jsonObject.has(field) || jsonObject.isNull(field)) {
            return false;
        }

        String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist
        if (value.isEmpty()) {
            return false;
        }

        switch (field) {
            case "datetime":
            case "timestamp":
            case "size":
            case "api_level":
                return Utils.isInteger(value);
            case "is_upgrade_supported":
                return "true".equals(value) || "false".equals(value);
            case "android_version":
                // Check if it's a valid Android version number
                return value.matches("^(\\d+)$|^(\\d+\\.\\d+)$|^(\\d+\\.\\d+\\.\\d+)$");
            case "md5sum":
                // Check if it's a valid MD5 checksum
                return value.matches("^[a-fA-F0-9]{32}$");
            case "filename":
                return value.endsWith(".zip");
            case "id":
                // Check if it's a valid SHA-256 checksum
                return value.matches("^[a-fA-F0-9]{64}$");
            case "url":
                // Check if it's a valid URL format
                return value.startsWith("http://") || value.startsWith("https://");
            default:
                return true;
        }
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -201,6 +201,12 @@ public class Utils {
                continue;
            }
            try {
                boolean isValidated = JsonValidator.validateResponseObject(
                        updatesList.getJSONObject(i));
                if (!isValidated) {
                    Log.d(TAG, "Ignoring incompatible update");
                    continue;
                }
                UpdateInfo update = parseJsonUpdate(updatesList.getJSONObject(i));
                if (!compatibleOnly || isCompatible(update)) {
                    updates.add(update);
@@ -512,4 +518,13 @@ public class Utils {
                context.getResources().getInteger(R.integer.battery_ok_percentage_discharging);
        return percent >= required;
    }

    public static boolean isInteger(String value) {
        try {
            Integer.parseInt(value);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}