1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
//! Checksums of migration scripts are used in various parts of the migration
//! engine to ensure integrity. This module contains common logic that should be
//! used everywhere for consistency.
/// Compute the checksum for a new migration script, and render it formatted to
/// a human readable string.
pub(crate) fn render_checksum(script: &str) -> String {
compute_checksum(script).format_checksum()
}
/// Returns whether a migration script matches an existing checksum.
pub(crate) fn script_matches_checksum(script: &str, checksum: &str) -> bool {
use std::iter::{once, once_with};
// Checksum with potentially different line endings, so checksums will match
// between Unix-like systems and Windows.
//
// This is necessary because git messes with line endings. For background
// information, read
// https://web.archive.org/web/20150912185006/http://adaptivepatchwork.com:80/2012/03/01/mind-the-end-of-your-line/
let mut script_checksums = once(compute_checksum(script))
.chain(once_with(|| compute_checksum(&script.replace("\r\n", "\n"))))
.chain(once_with(|| compute_checksum(&script.replace('\n', "\r\n"))));
script_checksums.any(|script_checksum| {
// Due to an omission in a previous version of the schema engine,
// some migrations tables will have old migrations with checksum strings
// that have not been zero-padded.
//
// Corresponding issue:
// https://github.com/prisma/prisma-engines/issues/1887
let script_checksum_str = if !checksum.is_empty() && checksum.len() != CHECKSUM_STR_LEN {
script_checksum.format_checksum_old()
} else {
script_checksum.format_checksum()
};
script_checksum_str == checksum
})
}
/// Checksumming implementation. This should be the single place where we do this.
fn compute_checksum(script: &str) -> [u8; 32] {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(script);
hasher.finalize().into()
}
/// The length (in bytes, or equivalently ascii characters) of the checksum
/// strings.
const CHECKSUM_STR_LEN: usize = 64;
/// Format a checksum to a hexadecimal string. This is used to checksum
/// migration scripts with Sha256.
trait FormatChecksum {
/// Format a checksum to a hexadecimal string.
fn format_checksum(&self) -> String;
/// Obsolete checksum method, should only be used for compatibility.
fn format_checksum_old(&self) -> String;
}
impl FormatChecksum for [u8; 32] {
fn format_checksum(&self) -> String {
use std::fmt::Write as _;
let mut checksum_string = String::with_capacity(32 * 2);
for byte in self {
write!(checksum_string, "{byte:02x}").unwrap();
}
assert_eq!(checksum_string.len(), CHECKSUM_STR_LEN);
checksum_string
}
// Due to an omission in a previous version of the schema engine,
// some migrations tables will have old migrations with checksum strings
// that have not been zero-padded.
//
// Corresponding issue:
// https://github.com/prisma/prisma-engines/issues/1887
fn format_checksum_old(&self) -> String {
use std::fmt::Write as _;
let mut checksum_string = String::with_capacity(32 * 2);
for byte in self {
write!(checksum_string, "{byte:x}").unwrap();
}
checksum_string
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_checksum_does_not_strip_zeros() {
assert_eq!(
render_checksum("hello"),
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
);
assert_eq!(render_checksum("abcd").len(), CHECKSUM_STR_LEN);
}
#[test]
fn script_matches_checksum_is_line_ending_agnostic() {
let scripts = &[
&["ab\ncd\nef\ngh\rab", "ab\r\ncd\r\nef\r\ngh\rab"],
&["ab\ncd\nef\ngh\rab\n", "ab\r\ncd\r\nef\r\ngh\rab\r\n"],
];
// for loops go brrrrrrrrr
for scripts in scripts {
for script in *scripts {
for other_script in *scripts {
assert!(script_matches_checksum(script, &render_checksum(other_script)),);
}
}
}
}
#[test]
fn script_matches_checksum_negative() {
assert!(!script_matches_checksum("abc", &render_checksum("abcd")));
assert!(!script_matches_checksum("abc\n", &render_checksum("abc")));
}
}