Skip to content

fix(xml-helpers): double escape in rewrite_element_attr and silent break in hwpx_paths scanner #201

Description

@pignuante

요약

pull reader로 XML을 다시 쓰거나 훑는 보조 코드 두 곳에 잠복 결함이 있습니다. 둘 다 지금 사용자 문서를 망가뜨리는 경로는 확인되지 않아 우선순위가 낮고, 성격이 비슷해(XML 이벤트를 다루는 작은 도우미) 한 이슈로 묶었습니다.

  1. rewrite_element_attr가 이미 escape된 원래 속성 값을 push_attribute로 다시 escape합니다(&amp; → &amp;amp;). 대상 요소가 숫자 속성만 가진 <hp:sz>라 지금은 무해합니다.
  2. CLI hwpx_paths 스캐너가 XML 오류를 만나면 Err(_) => break로 조용히 멈춥니다. census-hwp5 --companion은 이때 앞부분만 담긴 목록을 status: ok로 돌려줍니다.

배경 지식

이미 아는 내용이면 건너뛰어도 됩니다.

1. quick-xml pull reader와 속성 값

Reader는 XML을 이벤트(Start·Empty·Text·End…)로 하나씩 돌려줍니다. 이때 attr.value는 파일에 적힌 그대로의(escape된) 바이트입니다(a&amp;b). 반대로 BytesStart::push_attribute((key, value))는 value를 escape되지 않은 글자로 보고 escape해서 씁니다. 원래 값을 그대로 옮기려면 먼저 unescape하거나, escape하지 않는 API로 넣어야 합니다.

2. layout_hint_patch의 표 높이 보정

HWP5 → HWPX 변환 뒤, hwpforge-convert는 생성된 section XML을 한 번 더 읽어 표의 <hp:sz height>를 HWP5 조판 값으로 고칩니다. 이때 rewrite_element_attr가 그 요소의 속성을 전부 다시 만들어 씁니다.

3. hwpx_paths 스캐너와 census-hwp5

CLI의 analysis/hwpx_paths.rs는 section XML을 decode하지 않고 훑어서 요소 경로·개수 목록을 만듭니다. inspect는 먼저 문서를 decode하므로 깨진 XML이면 거기서 오류가 납니다. census-hwp5 --companion <hwpx>는 decode 없이 이 스캐너만 돌립니다.

재현

A. rewrite_element_attr 이중 escape

main 32d1435의 crates/hwpforge-convert/src/layout_hint_patch.rs test module에 임시 테스트를 넣어 돌렸습니다(커밋하지 않음).

#[test]
fn probe_rewrite_element_attr_escape() {
    let xml = r#"<hp:sz width="10" note="a&amp;b&lt;c" height="5"/>"#;
    let mut reader = Reader::from_str(xml);
    let ev = match reader.read_event().unwrap() { Event::Empty(e) => e.into_owned(), other => panic!("{other:?}") };
    let rebuilt = rewrite_element_attr(ev, "height", "9999").unwrap();
    let mut writer = Writer::new(Cursor::new(Vec::new()));
    writer.write_event(Event::Empty(rebuilt)).unwrap();
    println!("{}", String::from_utf8(writer.into_inner().into_inner()).unwrap());
}
in : <hp:sz width="10" note="a&amp;b&lt;c" height="5"/>
out: <hp:sz width="10" note="a&amp;amp;b&amp;lt;c" height="9999"/>
속성 기대 실제
height (대상) 9999 9999
note (대상 아님) a&amp;b&lt;c 그대로 a&amp;amp;b&amp;lt;c

실제 호출은 <hp:tbl> 바로 아래 <hp:sz>뿐이고, 그 속성(width·widthRelTo·height·heightRelTo·protect)은 숫자·열거값이라 &·<가 들어갈 일이 없습니다. 같은 도우미를 다른 요소에 쓰는 순간 드러나는 결함입니다.

B. 스캐너의 조용한 break

  1. 표 두 개가 든 Markdown을 HWPX로 만듭니다.

    | a | b |
    | - | - |
    | 1 | 2 |
    
    문단
    
    | c | d |
    | - | - |
    | 3 | 4 |

    hwpforge convert t.md -o two.hwpx

  2. Contents/section0.xml에서 첫 </hp:tbl> 바로 뒤에 <hp:t>x</hp:T>(닫는 태그 대소문자 불일치)를 넣어 two-broken.hwpx를 만듭니다.

  3. hwpforge --json census-hwp5 tests/fixtures/user_samples/sample-memo-basic.hwp --companion two-broken.hwpx -o census.json

  4. census.json의 companion.path_inventory를 셉니다. 비교로 hwpforge --json inspect two-broken.hwpx도 돌립니다.

입력 기대 실제
정상 two.hwpx 표 2개 status: ok, 목록 27개, tbl 2개
two-broken.hwpx, census-hwp5 오류 또는 경고 status: ok, 목록 14개, tbl 1개(오류 지점 뒤가 없음)
two-broken.hwpx, inspect 오류 DECODE_FAILED ... expected </hp:t>, but </hp:T> was found

영향

항목 A. 이중 escape B. 조용한 break
현재 사용자 영향 없음(대상 요소에 escape가 필요한 값이 없음) census-hwp5 --companion의 결과가 조용히 잘림. 진단 도구라 문서 자체는 바뀌지 않음
신호 없음 없음(status: ok)
드러나는 조건 이 도우미를 문자열 속성이 있는 요소에 쓸 때 companion HWPX가 well-formed가 아닐 때

선택지

결함 안 내용 장단점
A A1 원래 속성은 Attribute를 그대로 push_attribute(attr)로 옮기고, 대상 속성만 새 값으로 원래 바이트 보존. 가장 작은 변경
A A2 원래 값을 unescape한 뒤 push_attribute((key, value)) 동작은 맞지만 unescape 실패 처리가 더 생김
B B1 오류를 HwpxResult로 올려 HWPX_CENSUS_FAILED로 끝냄 fail-closed. 깨진 companion은 census를 못 함
B B2 그때까지의 목록을 두고 경고 필드(오류 위치·메시지)를 결과에 추가 부분 결과를 쓸 수 있음. 출력 스키마가 바뀜

관련 위치

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions