{"record":{"id":"f8956d9c7468ff12","repo":"MuntashirAkon/AppManager","slug":"didn-t-read-the-right-header-magic","errorCode":null,"errorMessage":"Didn't read the right header magic","messagePattern":"Didn't read the right header magic","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/backup/adb/AndroidBackupHeader.java","lineNumber":99,"sourceCode":"                // okay, it's a version we recognize.  if it's version 1, we may need\n                // to try two different PBKDF2 regimes to compare checksums.\n                final boolean pbkdf2Fallback = (mBackupFileVersion == 1);\n\n                s = readHeaderLine(backupStream);\n                mCompress = (Integer.parseInt(s) != 0);\n                s = readHeaderLine(backupStream);\n                if (s.equals(\"none\")) {\n                    // no more header to parse; we're good to go\n                } else if (mPassword != null && mPassword.length > 0) { // AES-256\n                    preCompressStream = decodeAesHeaderAndInitialize(mPassword, s, pbkdf2Fallback, backupStream);\n                } else {\n                    throw new IOException(\"Archive is encrypted but no password given\");\n                }\n            } else {\n                throw new IOException(\"Wrong header version: \" + s);\n            }\n        } else {\n            throw new IOException(\"Didn't read the right header magic\");\n        }\n\n        // okay, use the right stream layer based on compression\n        return mCompress ? new InflaterInputStream(preCompressStream) : preCompressStream;\n    }\n\n    @NonNull\n    public OutputStream write(@NonNull OutputStream backupStream) throws Exception {\n        // Write the global file header.  All strings are UTF-8 encoded; lines end\n        // with a '\\n' byte.  Actual backup data begins immediately following the\n        // final '\\n'.\n        //\n        // line 1: \"ANDROID BACKUP\"\n        // line 2: backup file format version, currently \"5\"\n        // line 3: compressed?  \"0\" if not compressed, \"1\" if compressed.\n        // line 4: name of encryption algorithm [currently only \"none\" or \"AES-256\"]\n        //\n        // When line 4 is not \"none\", then additional header data follows:","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/backup/adb/AndroidBackupHeader.java#L81-L117","documentation":"IOException from AndroidBackupHeader.read when the very first header line of the stream is not the 'ANDROID BACKUP' magic string. The header parser requires this exact magic before parsing version/encryption lines.","triggerScenarios":"The input stream does not start with 'ANDROID BACKUP' — e.g. the file is a raw TAR, a zip, an already-decrypted stream, or the file is corrupt/truncated at the start.","commonSituations":"Wrong file passed to the restore path (mixing .ab with converted .tar); backups mangled by editors or FTP text-mode transfers; user renamed a non-backup file to .ab.","solutions":["Confirm the input file is a real Android backup starting with the 'ANDROID BACKUP' magic","Do not pass converted TAR files into AndroidBackupHeader — use them directly","Re-obtain the backup file; check for transfer corruption (compare sizes/hashes)","Skip any wrapper/leading bytes only if your pipeline intentionally prepends data"],"exampleFix":"// before\nextractor.extract(wrongFile); // actually a .tar\n// after\ntry (DataInputStream in = new DataInputStream(new FileInputStream(abFile))) {\n    byte[] magic = \"ANDROID BACKUP\".getBytes(\"UTF-8\");\n    byte[] buf = new byte[magic.length];\n    in.readFully(buf);\n    if (!Arrays.equals(buf, magic)) throw new IOException(abFile + \" is not an Android backup\");\n}\nextractor.extract(abFile);","handlingStrategy":"validation","validationCode":"byte[] magic = \"ANDROID BACKUP\".getBytes(\"UTF-8\");\nbyte[] head = new byte[magic.length];\ntry (InputStream in = new FileInputStream(abFile)) {\n    if (in.read(head) != magic.length || !Arrays.equals(head, magic)) {\n        throw new IOException(abFile + \" is not an Android backup\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    InputStream tar = header.toTar();\n} catch (IOException e) {\n    if (e.getMessage().contains(\"header magic\")) {\n        Log.e(TAG, \"Input is not an Android backup stream\", e);\n    } else throw e;\n}","preventionTips":["Check the magic string before constructing the header parser","Ensure file extensions (.ab vs .tar) match actual formats","Compare file hashes after transfers to detect corruption","Keep wrapper-stream plumbing out of the header parser input"],"tags":["backup","header","magic"],"backgroundTag":"unexpected-response-shape","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}