빌드노트
curl --data @파일로 보냈더니 줄바꿈과 NUL이 사라졌다
curl --data @file은 파일의 CR·LF·NUL을 제거한다. 9바이트 합성 파일을 loopback으로 보내 수신 길이·hex·SHA-256을 비교하고, 직접 문자열 인자와 파일 읽기를 구별해 원본 바이트 보존에는 --data-binary를 고르는 이유를 설명한다.
curl --data "@payload.bin"로 파일을 보냈는데 서버에서 줄이 붙거나 원본 바이트 기준 검증이 어긋난다면, curl의 파일 읽기 옵션을 확인한다. --data는 @파일을 읽을 때 CR·LF·NUL을 제거한다. 원본 파일의 바이트를 서명 입력으로 삼는 요청에서는 --data-binary "@payload.bin"를 선택한다. curl의 --data 설명 (새 탭에서 열림)
--data-binary는 파일에서 읽은 줄바꿈을 포함해 바이트를 보존해서 본문으로 보낸다. 이름에 binary가 있어도 이미지나 압축 파일에만 쓰는 옵션은 아니다. 텍스트 파일도 줄 끝까지 같은 바이트로 보내야 한다면 이 선택이 필요하다. curl의 --data-binary 설명 (새 탭에서 열림)
입력이 어디서 오는지도 구별한다. --data "name=pen"처럼 문자열을 직접 인자로 주면 curl은 셸이 전달한 인자를 그대로 보낸다. @payload.bin은 파일을 읽으라는 지시이며, CR·LF·NUL 제거는 이 파일 읽기 경로의 규칙이다. 직접 입력한 문자열의 줄바꿈은 셸이 실제로 어떤 문자를 넘겼는지까지 확인해야 한다.
9바이트 파일이 4바이트로 도착했다
Windows 11, Windows PowerShell 5.1, curl 8.21.0에서 작은 합성 파일을 두 옵션으로 전송했다. 수신기는 Python 3.12.10으로 만든 로컬 HTTP 서버였고, 본문을 문자열이나 폼으로 파싱하기 전에 바이트 그대로 읽었다. 실제 웹훅이나 서명 키는 사용하지 않았다.
같은 입력을 만들려면 빈 작업 폴더의 PowerShell에서 다음 한 줄로 payload.bin을 만든다. 이 명령은 A 뒤에 LF, B 뒤에 CRLF, C 뒤에 NUL, D 뒤에 LF가 오는 9바이트를 쓴다. 편집기의 줄 끝 설정에 맡기지 않고 입력 바이트를 고정한다.
[IO.File]::WriteAllBytes((Join-Path $PWD 'payload.bin'), [byte[]](0x41,0x0A,0x42,0x0D,0x0A,0x43,0x00,0x44,0x0A))
전송을 비교하려면 POST 본문을 파싱하기 전에 길이·hex·SHA-256을 기록하는 자신의 로컬 테스트 수신기가 필요하다. 아래 주소는 이 실험의 수신 주소다. 자신의 수신기가 다른 포트나 경로를 쓴다면 두 명령의 주소를 같은 로컬 주소로 바꾼다. 응답 형식은 수신기 구현에 따라 달라지므로, 응답 화면보다 수신한 원시 본문을 비교한다.
다음 두 명령은 같은 파일에 파일 읽기 옵션만 다르게 적용한다. curl.exe를 사용하며 첫 옵션 --disable로 개인 curl 설정을 제외하고, --noproxy로 loopback 요청의 프록시 경유를 막았다.
curl.exe --disable --noproxy "*" --max-time 5 -sS --data "@payload.bin" "http://127.0.0.1:8765/inspect"
curl.exe --disable --noproxy "*" --max-time 5 -sS --data-binary "@payload.bin" "http://127.0.0.1:8765/inspect"
실제 수신 기록의 길이와 hex를 원본 파일과 나란히 놓으면 다음과 같다. hex에서 0a는 LF, 0d는 CR, 00은 NUL이다.
원본 파일 length=9 hex=41 0a 42 0d 0a 43 00 44 0a
--data 수신 length=4 hex=41 42 43 44
--data-binary 수신 length=9 hex=41 0a 42 0d 0a 43 00 44 0a
SHA-256도 비교했다. 원본과 --data-binary 수신 본문은 모두 e94b7f9ae1ad9d768904546cdc040221c935d86a50dbf41817842ea7e0c2d6b3이었다. --data 수신 본문은 e12e115acf4552b2568b55e93cbd39394c4ef81c82447fafc997882a02d23677로 달랐다. 줄바꿈뿐 아니라 NUL도 빠져 A·B·C·D만 남았다.
전송 성공과 바이트 보존을 따로 확인한다
두 명령의 curl 종료 코드는 모두 0이었다. 요청의 Content-Length는 각각 4와 9였고, 서버가 실제로 읽은 길이와 일치했다. 이 결과는 파일을 읽는 단계에서 CR·LF·NUL을 제거한다는 문서 설명과 맞는다. Content-Length를 원본 길이로 직접 덮어써도 사라진 바이트를 복원할 수는 없다.
두 요청의 기본 Content-Type은 모두 application/x-www-form-urlencoded였다. --data-binary를 선택해도 이 헤더가 자동으로 application/octet-stream으로 바뀌지는 않는다. 서버가 요구하는 본문 형식에 맞게 Content-Type을 정하되, 원본 바이트 보존은 파일 입력 옵션으로 확보해야 한다. 이 기본값도 --data-binary 공식 문서 (새 탭에서 열림)에 명시돼 있다.
연결 오류가 나거나 서버가 POST를 거절했다면 아직 본문 비교를 하지 못한 상태다. 먼저 로컬 수신 주소와 POST 처리 경로를 확인한다. --data-binary로 바꾼 뒤에도 바이트가 다르면 실제로 읽은 파일 경로와 파일 내용을 확인하고, 서버의 텍스트·폼 변환 이전에 기록한 바이트와 다시 비교한다. 파싱 뒤의 문자열 로그만으로 줄바꿈 보존 여부를 판단하지 않는다.
이번 확인 범위는 이 curl 버전에서 합성 파일을 로컬 HTTP 수신기로 보냈을 때의 본문 바이트다. 실제 서명 계산·검증이나 외부 웹훅의 동작은 실행하지 않았다. 따라서 이 비교는 파일 읽기 옵션을 고르는 근거이며, 개별 서비스의 서명 실패 원인을 확정한 결과는 아니다.