review(p6-1): 회차 2 지적 반영 — GPS 안전성 + 디버깅
- src/exif_extract.rs:
• `gps_decimal` 에 ±90 / ±180 범위 검증 추가. 비정상 EXIF (예: 위도
300°) 가 들어와도 wire 에 흘러나가지 않고 silent drop.
• GPSLatitudeRef / GPSLongitudeRef 가 빠진 좌표는 양수 가정으로
내보내지 않고 None 반환 — 모호한 부호를 그대로 두는 대신 손상된
메타데이터로 처리.
• `read_from_container` 실패 시 `tracing::debug!` 한 줄로 사유 기록
(운영시 \"EXIF 없음\" vs \"EXIF 손상\" 구분 단서).
- src/dims.rs: `match Some/None` 을 `anyhow::Context::context()?` 로
압축. import 한 줄 추가.
- src/lib.rs: `Vec::with_capacity` 를 dim_warning 분기에 따라
`2` / `3` 으로 정확히 맞추고 의미 주석 한 줄 추가.
- tests/common/mod.rs: `build_exif_blob_gps` 를 `GpsFlavor`
파라미터로 일반화 (`Valid` / `NoRef` / `OutOfRange`). JPEG 스플라이스
로직은 `splice_exif_into_jpeg` 헬퍼로 추출.
- tests/extractor.rs: 회귀 테스트 2건 추가 — `*Ref` 누락 좌표 드롭,
out-of-range 위도 드롭 (경도는 정상 통과 검증).
cargo test -p kebab-parse-image — 16건 (4 unit + 12 integration) pass.
cargo clippy -p kebab-parse-image --all-targets -- -D warnings — pass.
This commit is contained in:
@@ -52,8 +52,25 @@ pub fn no_exif_png() -> Vec<u8> {
|
||||
/// APP1 segment immediately after SOI (FF D8). The EXIF blob is built
|
||||
/// with `exif::experimental::Writer`.
|
||||
pub fn exif_with_gps_jpg() -> Vec<u8> {
|
||||
splice_exif_into_jpeg(build_exif_blob_gps(GpsFlavor::Valid))
|
||||
}
|
||||
|
||||
/// JPEG carrying GPSLatitude / GPSLongitude triples but missing the
|
||||
/// matching `*Ref` tags. Used to verify the extractor drops the GPS
|
||||
/// coordinates entirely (rather than silently assuming positive sign).
|
||||
pub fn exif_gps_no_ref_jpg() -> Vec<u8> {
|
||||
splice_exif_into_jpeg(build_exif_blob_gps(GpsFlavor::NoRef))
|
||||
}
|
||||
|
||||
/// JPEG carrying a GPSLatitude triple whose decimal value lands outside
|
||||
/// the legal `[-90, 90]` range (300° here). Used to verify the extractor
|
||||
/// drops the coordinate as corrupted.
|
||||
pub fn exif_gps_out_of_range_jpg() -> Vec<u8> {
|
||||
splice_exif_into_jpeg(build_exif_blob_gps(GpsFlavor::OutOfRange))
|
||||
}
|
||||
|
||||
fn splice_exif_into_jpeg(exif_blob: Vec<u8>) -> Vec<u8> {
|
||||
let base = encode_tiny_jpeg();
|
||||
let exif_blob = build_exif_blob_gps();
|
||||
|
||||
let mut out = Vec::with_capacity(base.len() + exif_blob.len() + 16);
|
||||
// SOI: FF D8.
|
||||
@@ -85,7 +102,21 @@ fn encode_tiny_jpeg() -> Vec<u8> {
|
||||
buf.into_inner()
|
||||
}
|
||||
|
||||
fn build_exif_blob_gps() -> Vec<u8> {
|
||||
/// Selector for which GPS shape the test fixture should embed.
|
||||
#[derive(Clone, Copy)]
|
||||
enum GpsFlavor {
|
||||
/// 37°30'0" N, 127°0'0" E with both `*Ref` tags (= 37.5, 127.0).
|
||||
Valid,
|
||||
/// Same DMS triples but `GPSLatitudeRef` / `GPSLongitudeRef` omitted.
|
||||
/// Extractor must treat this as corrupted metadata and drop the
|
||||
/// coordinates.
|
||||
NoRef,
|
||||
/// Latitude DMS encodes 300° (out of the legal `[-90, 90]` range).
|
||||
/// Extractor must drop the coordinate.
|
||||
OutOfRange,
|
||||
}
|
||||
|
||||
fn build_exif_blob_gps(flavor: GpsFlavor) -> Vec<u8> {
|
||||
let make = Field {
|
||||
tag: Tag::Make,
|
||||
ifd_num: In::PRIMARY,
|
||||
@@ -111,13 +142,17 @@ fn build_exif_blob_gps() -> Vec<u8> {
|
||||
ifd_num: In::PRIMARY,
|
||||
value: Value::Short(vec![1]),
|
||||
};
|
||||
// GPS — 37.5 N, 127.0 E (Seoul-ish). DMS triple: 37°30'0" N,
|
||||
// 127°0'0" E. Each component is num/denom rationals.
|
||||
let (lat_deg, lon_deg) = match flavor {
|
||||
GpsFlavor::OutOfRange => (300_u32, 127_u32),
|
||||
_ => (37_u32, 127_u32),
|
||||
};
|
||||
// GPS DMS triples — `OutOfRange` puts 300° in the latitude degrees
|
||||
// slot so the resulting decimal escapes ±90.
|
||||
let lat = Field {
|
||||
tag: Tag::GPSLatitude,
|
||||
ifd_num: In::PRIMARY,
|
||||
value: Value::Rational(vec![
|
||||
Rational { num: 37, denom: 1 },
|
||||
Rational { num: lat_deg, denom: 1 },
|
||||
Rational { num: 30, denom: 1 },
|
||||
Rational { num: 0, denom: 1 },
|
||||
]),
|
||||
@@ -131,7 +166,7 @@ fn build_exif_blob_gps() -> Vec<u8> {
|
||||
tag: Tag::GPSLongitude,
|
||||
ifd_num: In::PRIMARY,
|
||||
value: Value::Rational(vec![
|
||||
Rational { num: 127, denom: 1 },
|
||||
Rational { num: lon_deg, denom: 1 },
|
||||
Rational { num: 0, denom: 1 },
|
||||
Rational { num: 0, denom: 1 },
|
||||
]),
|
||||
@@ -149,9 +184,11 @@ fn build_exif_blob_gps() -> Vec<u8> {
|
||||
writer.push_field(&datetime);
|
||||
writer.push_field(&orientation);
|
||||
writer.push_field(&lat);
|
||||
writer.push_field(&lat_ref);
|
||||
writer.push_field(&lon);
|
||||
writer.push_field(&lon_ref);
|
||||
if !matches!(flavor, GpsFlavor::NoRef) {
|
||||
writer.push_field(&lat_ref);
|
||||
writer.push_field(&lon_ref);
|
||||
}
|
||||
|
||||
let mut blob = Cursor::new(Vec::new());
|
||||
writer
|
||||
|
||||
Reference in New Issue
Block a user