사전 Content-Type
표준

Content-Type

gabury1고친 사람 github-actions[bot]

Content-Type 은 지금 보내는 본문을 무엇으로 읽어야 하는지 받는 쪽에 알려 줍니다. 보내는 쪽이 본문에 형식 이름을 한 줄 붙여 보냅니다. 받는 쪽은 그 이름을 보고 본문을 어떻게 다룰지 정합니다.

쉽고 빠른 이해

Content-Type 은 본문에 붙는 형식 이름입니다. 서버가 Content-Type: text/html 을 붙여 보내면 받는 쪽은 그 본문을 웹 페이지로 다룹니다.

이게 없으면 받는 쪽은 같은 바이트를 무엇으로 열지 스스로 짐작해야 합니다. 짐작이 빗나가면 그림이라던 파일이 웹 페이지로 열립니다. 그 안에 심어 둔 코드가 돌 수도 있습니다.

이렇게 돕니다.

  1. 본문을 보내는 쪽이 형식 이름을 이 헤더에 적습니다. 본문이 없거나 그 형식을 모를 때는 안 적습니다
  2. 받는 쪽은 압축이 걸려 있으면 먼저 풉니다. 그다음 이 이름대로 본문을 읽습니다
  3. 요청을 받은 서버가 다루지 않는 형식이면 그 요청을 거절할 수 있습니다

대가는 보내는 쪽이 적은 값을 믿어야 한다는 것입니다. 서버 설정이 틀려 엉뚱한 값이 붙는 일이 있습니다. 그러면 받는 쪽은 틀린 값을 따르거나, 내용을 들여다보고 짐작합니다.

상세

Content-Type 은 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 메시지에 붙는 헤더 필드입니다. 헤더는 본문 앞에 놓여 그 메시지를 어떻게 다룰지 알리는 줄들입니다. 이 필드에는 본문의 형식을 가리키는 이름 하나가 들어갑니다. 그 이름을 미디어 타입이라고 부릅니다.

웹 페이지를 돌려주는 응답이라면 이 필드가 이렇게 적힙니다.

Content-Type: text/html; charset=ISO-8859-4

text/html 이 미디어 타입입니다. 받는 쪽은 이 본문을 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)로 적힌 글로 읽습니다. 뒤에 붙은 charset 값은 그 글자를 바이트로 적은 방식을 알립니다.

미디어 타입이 정하는 두 가지

미디어 타입 하나가 두 가지를 함께 정합니다. 데이터가 어떤 형식으로 적혔는지가 하나입니다. 받는 쪽이 그 데이터를 어떻게 처리하기로 되어 있는지가 다른 하나입니다.

둘째가 이 필드가 있어야 하는 까닭입니다. 서로 다른 미디어 타입이 같은 데이터 형식을 나눠 쓰는 일이 흔합니다. 그런 타입들은 처리 방법만 다릅니다. 이 차이는 데이터만 들여다봐서는 가릴 수 없습니다. 그래서 보내는 쪽이 이름으로 알려 줘야 합니다.

같은 글자 바이트라도 text/html 이면 웹 페이지로 그립니다. text/plain 이면 글자를 있는 그대로 보여 줍니다.

요청과 응답

본문을 실은 메시지라면 요청이든 응답이든 이 필드가 붙습니다. 응답에서는 서버가 돌려주는 본문의 형식을 알립니다. 요청에서는 클라이언트가 올려 보내는 본문의 형식을 알립니다. 브라우저가 폼을 POST 로 보낼 때가 요청 쪽의 흔한 예입니다.

본문을 담아 메시지를 만드는 쪽은 이 필드를 붙이는 것이 좋습니다(SHOULD). 보내는 쪽이 그 본문의 형식을 모를 때만 예외입니다. SHOULD 는 이유가 있으면 어겨도 되는 권고입니다. 반드시 지켜야 하는 MUST 와 다릅니다.

같은 미디어 타입 이름은 Accept 헤더에도 쓰입니다. Content-Type 은 「지금 보내는 본문이 이 형식이다」를 적습니다. Accept 는 「응답을 이 형식으로 받고 싶다」를 적습니다.

값의 생김새

값은 타입/서브타입 꼴입니다. 슬래시 앞이 큰 분류입니다. 뒤가 그 안의 구체 형식입니다. 큰 분류는 등록된 목록에서 고릅니다. application · audio · font · image · message · model · multipart · text · video 같은 이름입니다. 타입과 서브타입은 대소문자를 가리지 않습니다.

서브타입 뒤에는 세미콜론을 찍고 이름=값 꼴의 매개변수를 붙여도 됩니다(MAY). MAY 는 해도 되고 안 해도 된다는 선택입니다. 매개변수는 그 형식을 처리하는 데 쓰는 덧붙임 정보입니다. 매개변수 이름은 대소문자를 가리지 않습니다. 값이 대소문자를 가리는지는 매개변수마다 다릅니다. = 양옆에는 공백을 둘 수 없습니다.

flowchart TD
    V["Content-Type 값<br/>text/html;charset=utf-8"]
    V --> M["미디어 타입 · text/html"]
    V --> P["매개변수 · charset=utf-8<br/>세미콜론으로 여럿 이을 수 있다"]
    M --> T["타입 · text"]
    M --> S["서브타입 · html"]

그래서 아래 네 줄은 모두 같은 뜻입니다. 넷 다 UTF-8(Unicode Transformation Format 8-bit, 유니코드 변환 형식 8비트)로 적은 HTML 글을 가리킵니다. 일관성을 위해 첫 줄 꼴을 권합니다.

text/html;charset=utf-8
Text/HTML;Charset="utf-8"
text/html; charset="utf-8"
text/html;charset=UTF-8

둘째 줄은 타입과 매개변수 이름의 대소문자가 달라도 같습니다. 셋째 줄은 세미콜론 뒤에 공백이 붙고 값이 따옴표에 싸여도 같습니다. 따옴표로 감싼 값과 안 감싼 값은 같은 값으로 칩니다. 넷째 줄은 charset 값의 대소문자가 달라도 같습니다. charset 이름은 대소문자를 가리지 않기 때문입니다.

charset 매개변수

charset 은 글로 된 본문이 어떤 문자 인코딩으로 적혔는지 알리는 매개변수입니다. 문자 인코딩은 글자를 바이트로 바꾸는 규칙입니다. 같은 글자라도 규칙이 다르면 바이트가 달라집니다.

multipart 와 boundary

큰 분류가 multipart 인 타입은 본문 하나에 여러 조각을 나눠 담습니다. 조각마다 자기 형식이 따로 있습니다. 이 타입에는 boundary 매개변수가 붙습니다. 그 값이 조각과 조각 사이에 끼우는 경계 문자열입니다.

조각 사이의 줄바꿈은 CRLF(Carriage Return Line Feed, 캐리지 리턴과 줄 바꿈 두 글자)로만 적어야 합니다(MUST). multipart 본문 안에는 조각을 가르는 줄과 조각 헤더가 함께 들어 있습니다. 이런 본문은 내용 덩어리가 아니라 프로토콜 요소 하나로 다뤄집니다.

경계 문자열은 본문을 조각낼 때만 씁니다. HTTP 가 메시지 본문이 어디서 끝나는지 잴 때는 이 문자열을 쓰지 않습니다. 본문을 만들거나 해석하는 프로그램이 조각을 찾는 데 쓸 수는 있습니다. 조각 하나가 어떻게 생겼는지는 아래 예시 절에 있습니다.

받는 쪽이 본문을 여는 순서

이 값이 가리키는 것은 압축을 푼 뒤의 형식입니다. 본문을 gzip 으로 압축해 보냈다면 그 압축은 Content-Encoding 이라는 다른 헤더가 적습니다. 받는 쪽은 Content-Encoding 에 적힌 압축을 먼저 풉니다. 그다음에 Content-Type 대로 읽습니다.

필드가 아예 없을 때도 있습니다. 그때 받는 쪽은 본문을 application/octet-stream 으로 봐도 됩니다. 형식을 모르는 바이트 덩어리라는 뜻의 미디어 타입입니다.

아니면 데이터를 살펴 형식을 정해도 됩니다(MAY). 둘 중 무엇을 고를지는 받는 쪽에 맡겨져 있습니다. 내용을 들여다보고 쓸 타입을 정하는 이 일을 MIME 스니핑이라고 부릅니다.

MIME(Multipurpose Internet Mail Extensions, 다목적 인터넷 메일 확장)은 전자메일 규격입니다. 미디어 타입은 예전에 MIME 타입이라고 불렸습니다.

flowchart TD
    A["본문 바이트가 도착한다"] --> B{"Content-Encoding 이 있나"}
    B -->|있다| C["적힌 압축을 푼다"]
    B -->|없다| D{"Content-Type 이 있나"}
    C --> D
    D -->|있다| E["그 미디어 타입대로 처리한다"]
    D -->|없다| F["application/octet-stream 으로 보거나<br/>데이터를 살펴 형식을 정한다"]

출처 문서

Content-Type 의 정본은 RFC 9110 HTTP Semantics 의 §8.3 입니다. RFC(Request for Comments)는 번호를 하나씩 매겨 내는 인터넷 문서 계열입니다. RFC 9110 은 그중 IETF(Internet Engineering Task Force, 국제 인터넷 표준화 기구)가 낸 문서입니다.

RFC 9110 은 2022년 6월에 나왔습니다. Category 칸은 Standards Track 입니다. 표준으로 가는 궤도에 오른 문서라는 뜻입니다. STD(Standard) 번호 97 을 받았습니다. 편집자는 R. Fielding · M. Nottingham · J. Reschke 입니다.

RFC 9110 은 RFC 7231 을 폐기했습니다. RFC 2818 · 7232 · 7233 · 7235 · 7538 · 7615 · 7694 와 RFC 7230 의 일부도 함께 폐기했습니다. 옛 판의 절 번호를 인용하면 폐기된 판을 드는 것입니다.

절 무엇을 정하나
§8.3 Content-Type 의 뜻 · 붙이는 쪽의 SHOULD · 없을 때 받는 쪽의 MAY · 스니핑 위험 · 여러 번 실린 경우
§8.3.1 미디어 타입의 꼴 · 대소문자 · 같은 뜻의 네 줄
§5.6.6 매개변수의 꼴 · = 둘레 공백 금지 · 따옴표 유무가 같다는 것
§8.3.2 charset 이름이 매개변수로 온다는 것 · charset 이름의 대소문자 무시
§8.3.3 multipart 의 boundary 매개변수 · CRLF 만 쓰는 줄바꿈
§8.4 Content-Encoding · 압축해도 미디어 타입은 그대로라는 것
§15.5.16 415 Unsupported Media Type

판마다 이 필드를 정한 절

문서 나온 때 절 상태 본문을 부르는 말
RFC 2616 1999년 6월 §14.17 RFC 7230~7235 여섯 편이 폐기 entity-body
RFC 7231 2014년 6월 §3.1.1.5 RFC 9110 이 폐기 payload body
RFC 9110 2022년 6월 §8.3 현행 · STD 97 content

RFC 2616 에서 RFC 9110 까지는 두 단계입니다. RFC 2616 은 RFC 7230~7235 여섯 편으로 갈렸습니다. 그중 Content-Type 을 담은 RFC 7231 을 RFC 9110 이 폐기했습니다. RFC 7231 과 RFC 9110 의 정의 문장은 거의 같습니다. 본문을 「payload body」 라 부르던 것을 「content」 로 바꾼 정도입니다. RFC 2616 은 이 필드를 entity-header 필드라고 불렀습니다.

flowchart TD
    A["RFC 2616"] --> B["RFC 7230 · 일부만"]
    A --> C["RFC 7231 · Content-Type 을 담은 판"]
    A --> D["RFC 7232 · 7233 · 7235"]
    A --> E["RFC 7234 · RFC 9110 의 폐기 목록에 없다"]
    B --> F["RFC 9110 · 현행 · STD 97"]
    C --> F
    D --> F
    G["그 밖 넷 · RFC 2818 · 7538 · 7615 · 7694"] --> F

요구 강도

요구 강도 낱말의 뜻은 RFC 9110 §2.2 가 정합니다. MUST · SHOULD · MAY 같은 낱말을 BCP(Best Current Practice) 14, 곧 RFC 2119 와 RFC 8174 에 적힌 대로 읽습니다. 전부 대문자로 적혔을 때, 그리고 그때만 그렇게 읽습니다.

무엇을 누가 강도 절
본문을 담은 메시지에 Content-Type 을 붙인다. 형식을 모르면 예외 보내는 쪽 SHOULD RFC 9110 §8.3
필드가 없으면 application/octet-stream 으로 보거나 데이터를 살펴 정한다 받는 쪽 MAY RFC 9110 §8.3
서브타입 뒤에 매개변수를 붙인다 보내는 쪽 MAY RFC 9110 §8.3.1
multipart 조각 사이 줄바꿈을 CRLF 로만 적는다 보내는 쪽 MUST RFC 9110 §8.3.3
경계 문자열이 조각 안에 나타나지 않게 한다 보내는 쪽 MUST NOT RFC 7578 §4.1
form-data 조각마다 Content-Disposition 을 form-data 와 name 매개변수로 단다 보내는 쪽 MUST RFC 7578 §4.2
조각에 Content-Type 을 단다. 없으면 text/plain 으로 본다 보내는 쪽 MAY RFC 7578 §4.4
파일 조각에 맞는 미디어 타입을 단다. 모르면 application/octet-stream 보내는 쪽 SHOULD RFC 7578 §4.4
요청 본문에 걸린 압축 방식을 못 받으면 415 로 답한다 오리진 서버 MAY RFC 9110 §8.4

대문자가 아닌 문장도 있습니다. RFC 9110 은 미디어 타입을 IANA(Internet Assigned Numbers Authority, 인터넷 번호 할당 기관)에 등록하는 것이 마땅하다(ought to)고만 적습니다. 스니핑을 구현하면 사용자가 끌 수단을 주라는 문장도 권장(encouraged)입니다. 둘 다 BCP 14 요구가 아닙니다.

값이 등록되는 곳

미디어 타입 이름은 IANA 의 Media Types 레지스트리가 받습니다. 큰 분류마다 표가 하나씩 있습니다. application · audio · example · font · haptics · image · message · model · multipart · text · video 입니다. 등록 절차는 RFC 6838 이 정합니다.

이 레지스트리는 q 라는 이름의 매개변수를 받지 않습니다. Accept 헤더는 q 로 선호도를 조절합니다. 같은 이름의 매개변수가 있으면 그 쓰임과 부딪힙니다.

다른 문서가 맡는 것

WHATWG(Web Hypertext Application Technology Working Group)는 HTML 과 브라우저 동작 표준을 내는 작업 그룹입니다. RFC 9110 은 MIME 규격인 RFC 2046 의 미디어 타입을 씁니다.

문서 상태 이 필드와 맞닿는 대목
RFC 7578 2015년 7월 · Standards Track · RFC 2388 폐기 multipart/form-data 의 boundary 와 조각 헤더
RFC 6713 2012년 8월 · Informational application/gzip 미디어 타입 등록
HTML Living Standard §4.10.19.6 · 폼 전송 알고리즘 WHATWG 의 계속 고쳐지는 표준 폼의 enctype 이 요청의 Content-Type 이 되는 단계
MIME Sniffing Standard §1 · §7 WHATWG 의 계속 고쳐지는 표준 브라우저가 내용을 보고 타입을 정하는 절차
Fetch Standard §3.6 WHATWG 의 계속 고쳐지는 표준 X-Content-Type-Options: nosniff 의 처리

표준이 정하지 않고 남긴 것

사용자 에이전트는 브라우저처럼 사용자를 대신해 요청을 보내는 프로그램입니다. RFC 9110 은 일부 사용자 에이전트가 어떤 경우에는 내용을 보고 받은 타입을 덮어쓴다고만 적습니다. 그 절차는 정하지 않고 MIME Sniffing Standard 를 가리킵니다.

필드가 없을 때 받는 쪽이 octet-stream 으로 볼지 데이터를 살필지도 고르지 않았습니다. 둘 다 MAY 입니다. 필드가 여러 번 실려 온 오류를 어떻게 처리할지도 정하지 않았습니다. 받는 쪽들이 흔히 마지막으로 문법이 맞는 값을 쓴다고 적을 뿐입니다.

예시

Content-Type 값 세 벌입니다. 서버가 보내는 응답 하나와 브라우저가 보내는 폼 요청 둘입니다. 값마다 무엇을 정하는지를 아래에 적습니다.

text/html 응답

Content-Type: text/html; charset=ISO-8859-4

웹 페이지를 돌려주는 응답의 헤더 한 줄입니다. 본문이 HTML 글이라는 것을 text/html 이 알립니다. 그 글이 ISO-8859-4 라는 문자 인코딩으로 적혔다는 것을 charset 매개변수가 알립니다. 받는 쪽은 이 인코딩으로 바이트를 글자로 되돌린 뒤 웹 페이지로 처리합니다.

multipart/form-data 요청 본문

파일과 입력값을 한 요청에 담는 폼 전송은 multipart/form-data 를 씁니다. 브라우저가 경계 문자열을 하나 만들어 바깥 Content-Type 의 boundary 매개변수에 넣습니다. 경계 문자열이 AaB03x 라면 바깥 헤더는 이렇습니다.

Content-Type: multipart/form-data; boundary=AaB03x

본문은 경계 문자열 앞에 -- 를 붙인 줄로 조각을 가릅니다. 사용자가 field1 입력란에 「Joe owes €100」 을 쳤을 때 조각이 이런 꼴일 수 있습니다.

--AaB03x
content-disposition: form-data; name="field1"
content-type: text/plain;charset=UTF-8
content-transfer-encoding: quoted-printable

Joe owes =E2=82=AC100.
--AaB03x
줄 뜻
--AaB03x 조각이 시작된다. 경계 문자열 앞에 -- 를 붙였다
content-disposition: form-data; name="field1" 폼의 field1 입력란 값이다. 조각마다 반드시 단다(MUST)
content-type: text/plain;charset=UTF-8 이 조각은 UTF-8 로 적은 글이다. 생략하면 text/plain 으로 본다
content-transfer-encoding: quoted-printable 바이트를 =E2 처럼 등호와 16진수 두 자리로 적었다
Joe owes =E2=82=AC100. 유로 기호가 UTF-8 세 바이트 E2 82 AC 로 적혔다
--AaB03x 다음 조각이 시작된다

바깥 헤더와 조각은 이렇게 놓입니다.

flowchart TD
    H["바깥 헤더<br/>Content-Type: multipart/form-data; boundary=AaB03x"]
    B1["--AaB03x"]
    subgraph P1["조각 · field1 입력란"]
        D1["조각 헤더 · content-disposition"] --> T1["조각 헤더 · content-type: text/plain;charset=UTF-8"]
        T1 --> V1["조각 본문 · Joe owes =E2=82=AC100."]
    end
    B2["--AaB03x · 다음 조각"]
    H --> B1
    B1 --> P1
    P1 --> B2

바깥 Content-Type 은 본문이 조각으로 나뉘었다는 것과 경계 문자열만 알립니다. 조각의 형식은 조각 안의 content-type 이 알립니다. 파일을 실은 조각은 그 파일에 맞는 미디어 타입을 다는 것이 좋습니다(SHOULD). 모르면 application/octet-stream 을 답니다.

실무에서는 조각마다 charset 을 달지 않는 구현이 많습니다. 그런 구현은 폼 본문 한 벌에 하나씩 있는 기본 charset 에 기댑니다. 그 기본값을 무엇이 정하는지는 이 편의 자료에 없습니다(미확인).

폼의 enctype 이 정하는 요청 값

HTML 폼은 enctype 속성으로 본문을 어떻게 적을지 고릅니다. 고를 수 있는 값은 셋입니다. 속성을 빼거나 틀린 값을 적으면 application/x-www-form-urlencoded 가 됩니다. 폼을 POST 로 보낼 때 브라우저는 고른 값을 요청의 Content-Type 으로 씁니다.

enctype 요청의 Content-Type
application/x-www-form-urlencoded · 기본값 application/x-www-form-urlencoded
multipart/form-data multipart/form-data; boundary= 뒤에 브라우저가 만든 경계 문자열
text/plain text/plain

개발자가 폼에 Content-Type 을 직접 적지 않아도 요청에는 이 값이 붙습니다. multipart/form-data 에만 경계 문자열이 매개변수로 더 붙습니다.

실패

Content-Type 이 빠지거나 본문과 안 맞을 때 그 메시지를 받은 쪽이 무엇을 하는지 봅니다.

조건 받는 쪽이 하는 일 결과
필드가 없다 application/octet-stream 으로 보거나 데이터를 살펴 형식을 정한다(MAY) 받는 쪽마다 고른 길이 다를 수 있다
값이 본문과 안 맞는다 일부 사용자 에이전트가 어떤 경우에는 내용을 보고 받은 타입을 덮어쓴다 틀린 결론을 낼 위험. 그 때문에 보안 위험에 더 노출될 수 있다
서버가 그 형식을 처리하지 않는다 요청을 처리하지 않고 거절할 수 있다 415 Unsupported Media Type
필드가 여러 번 실렸다 흔히 마지막으로 문법이 맞는 값을 쓴다 구현마다 처리가 갈리면 구현끼리 결과가 어긋나고 보안 문제가 생길 수 있다
nosniff 가 붙은 응답의 타입이 스크립트·스타일시트 요청과 안 맞는다 브라우저가 응답을 막는다 막힌 응답(blocked)

MIME 스니핑

MIME 스니핑은 브라우저가 Content-Type 만 믿지 않고 본문 내용도 들여다봐 쓸 타입을 정하는 일입니다. 스니핑(sniffing)은 냄새를 맡는다는 뜻입니다.

자원 소유자가 그 표현에 맞는 Content-Type 을 내주도록 오리진 서버를 늘 제대로 설정해 두지는 않습니다. 본문과 안 맞는 Content-Type 을 보내는 서버가 많습니다. 브라우저는 그런 서버를 받아 주려고 내용을 함께 살폈습니다.

브라우저마다 짐작하는 법이 달랐습니다. 각 브라우저는 다른 브라우저의 절차를 거꾸로 짐작해 맞추려 했습니다. 그 노력이 다 들어맞지는 않았습니다. 그래서 서버가 뜻한 것과 다른 타입으로 응답을 해석하는 일이 생겼습니다. 그중 일부는 보안 문제로 이어졌습니다.

미디어 타입에는 받는 쪽이 그 본문으로 무엇까지 할 수 있는지가 딸려 옵니다. 그림 타입은 화면에 그리는 것으로 끝납니다. HTML 은 안에 든 스크립트를 실행할 수 있는 타입입니다.

그래서 그림 타입은 권한이 낮습니다. HTML 은 권한이 높습니다. 낮은 권한으로 내보낸 것이 높은 권한으로 읽히는 것을 권한 상승이라고 부릅니다.

가장 심한 경우는 정직한 서버가 사용자가 올린 파일을 낮은 권한의 타입으로 내보낼 때입니다. 서버는 그 파일을 그림으로 여겨 해가 없다고 봅니다. 브라우저는 내용을 보고 HTML 로 판단합니다. 공격자는 이 틈으로 다른 사용자의 인증 정보를 훔치거나 교차 사이트 스크립팅 공격을 걸 수 있습니다.

sequenceDiagram
    participant U as 올린 사람
    participant S as 서버
    participant B as 다른 사용자의 브라우저
    U->>S: 그림이라며 파일을 올린다 (속은 HTML 과 스크립트)
    B->>S: 그 파일을 요청한다
    S-->>B: 그림 타입을 붙여 내려보낸다
    Note over B: 내용을 보고 HTML 로 판단한다
    Note over B: 스크립트가 이 사용자 화면에서 돈다

악의적인 서버라면 애초에 아무 타입이나 붙일 수 있습니다. 그래서 이 위험은 정직한 서버가 남의 파일을 내보낼 때의 이야기입니다.

스니핑 절차가 늘 받은 타입을 덮어쓰는 것은 아닙니다. 절차는 받은 타입을 먼저 봅니다. 받은 타입이 XML(eXtensible Markup Language, 확장 가능 마크업 언어) 타입이거나 HTML 타입이면 거기서 끝납니다. 계산된 타입은 받은 타입 그대로입니다.

받은 타입이 아예 없을 때는 내용을 살펴 타입을 찾습니다. unknown/unknown · application/unknown · */* 처럼 형식을 모른다는 값일 때도 내용을 살핍니다.

스니핑에는 다른 위험도 있습니다. 상세에서 봤듯이 같은 데이터 형식을 나눠 쓰고 처리 방법만 다른 미디어 타입이 흔합니다. 이 차이는 데이터만 봐서는 가릴 수 없습니다. 내용으로 짐작하면 이 차이를 잘못 짚을 수 있습니다.

nosniff

응답에 X-Content-Type-Options 헤더를 붙이면 그 응답의 Content-Type 을 요청의 목적지와 대조하게 만들 수 있습니다. 목적지는 브라우저가 그 응답을 무엇으로 쓰려고 받아 왔는지입니다. 스크립트 · 스타일시트 · 그림 같은 것입니다.

값은 nosniff 하나뿐입니다. 대소문자는 가리지 않습니다. 브라우저는 이 헤더의 첫 값이 nosniff 일 때만 켜진 것으로 봅니다. 켜지면 목적지와 안 맞는 타입의 응답이 막힙니다.

요청이 불러오려는 것 막는 조건
스크립트 응답 타입을 못 읽었거나 자바스크립트 타입이 아니다
스타일시트 응답 타입을 못 읽었거나 text/css 가 아니다
그림 따지지 않는다

스크립트와 스타일시트만 보는 까닭은 알려진 공격이 그 둘에 걸려 있어서입니다. 그림은 이미 배포된 웹 콘텐츠와 호환이 안 돼 넣지 않았습니다.

415 거절

415 Unsupported Media Type 은 오리진 서버가 요청을 처리하지 않고 거절한다는 상태 코드입니다. 그 메서드로 그 대상에 보낸 본문 형식을 지원하지 않는다는 뜻입니다. 형식 문제의 원인은 요청이 적은 Content-Type 일 수 있습니다. Content-Encoding 일 수도 있습니다. 서버가 데이터를 직접 살펴본 결과일 수도 있습니다.

요청 본문에 걸린 압축 방식을 받을 수 없을 때 오리진 서버는 415 로 답해도 됩니다(MAY).

원인이 미디어 타입이면 서버는 응답에 Accept 헤더를 실어 받을 수 있었던 미디어 타입을 알릴 수 있습니다. 원인이 압축 방식이면 Accept-Encoding 헤더로 받을 수 있었던 방식을 알리는 것이 마땅합니다. 요청에 실린 Accept 는 「이 형식으로 받고 싶다」를 적습니다. 415 응답에 실린 Accept 는 「이 요청에서라면 이 형식들을 받을 수 있었다」를 알립니다.

여러 번 실린 필드

Content-Type 은 한 메시지에 한 번만 오는 필드로 정의돼 있습니다. 그런데 잘못 두 번 이상 생성되는 일이 있습니다. 그러면 값이 목록처럼 이어 붙은 꼴로 도착합니다.

받는 쪽은 흔히 그 목록에서 마지막으로 문법이 맞는 값을 골라 이 오류를 넘깁니다. 구현마다 오류 처리가 다르면 구현끼리 결과가 어긋나는 상호운용 문제와 보안 문제가 생길 수 있습니다. 한 구현은 이 값을, 다른 구현은 저 값을 믿게 되기 때문입니다.

경계

gzip 으로 압축해 보낸 HTML 응답의 Content-Type 은 application/gzip 일까요? 아닙니다. text/html 입니다.

압축은 Content-Encoding 이 맡습니다. Content-Encoding 은 미디어 타입이 원래 가진 것 말고 본문에 더 씌운 코딩을 적는 헤더입니다. 코딩은 gzip 압축처럼 데이터에 한 겹 더 씌운 변환을 말합니다. 미디어 타입이 원래 가진 코딩이란 늘 압축된 형식처럼 그 타입 자신에 붙어 있는 코딩입니다.

이 헤더는 주로 미디어 타입의 정체를 잃지 않고 데이터를 압축하려고 씁니다. Content-Type 은 그 코딩을 푼 뒤의 형식을 적습니다.

Content-Type: text/html; charset=utf-8
Content-Encoding: gzip

반대로 .gz 파일 자체를 내려보낼 때는 application/gzip 입니다. application/gzip 은 gzip 으로 압축한 데이터 덩어리를 가리키는 미디어 타입입니다. 확장자는 gz 입니다. 첫 두 바이트는 0x1f 0x8b 입니다.

이때 압축은 그 미디어 타입 자신의 성질입니다. 그런 압축은 Content-Encoding 에 다시 적지 않습니다.

flowchart TD
    subgraph S1["압축해서 보낸 HTML 문서"]
        subgraph S2["바깥 겹 · gzip · Content-Encoding 이 적는다"]
            I1["속 · HTML 문서 · Content-Type 이 적는다"]
        end
    end
    subgraph S3[".gz 파일 그 자체"]
        I2["겹 하나 · gzip 데이터 · Content-Type 이 적는다"]
    end
보내는 것 Content-Type Content-Encoding 받는 쪽
압축해서 보낸 HTML 문서 text/html gzip 압축을 풀고 웹 페이지로 처리한다
.gz 파일 그 자체 application/gzip 없음 압축된 파일로 받는다

같은 데이터를 두 가지 응답으로 내놓는 오리진 서버도 있을 수 있습니다. 압축을 Content-Type 쪽에 적느냐 Content-Encoding 쪽에 적느냐만 다른 두 응답입니다.

일부 사용자 에이전트는 두 응답을 다르게 다룹니다. 예를 들어 Content-Type 이 application/gzip 인 응답에는 「다른 이름으로 저장」 창을 띄웁니다. Content-Encoding 이 gzip 인 응답은 압축을 저절로 풀어 화면에 그립니다.

Content-Encoding 이 걸리면 본문을 설명하는 다른 헤더 값은 따로 밝히지 않는 한 압축된 꼴을 기준으로 합니다. Content-Type 은 정의에서 압축을 푼 뒤의 형식이라고 따로 밝혀 둔 헤더입니다.

관련 항목

이것이 속하는 상위 분류

헤더 · 필드 · 표현 메타데이터 · 메타데이터 · 표현 · HTTP · 메시지

이 필드에 적히는 값과 그 조각

미디어 타입 · 서브타입 · 미디어 타입 매개변수 · Content-Type charset · 문자 인코딩 · boundary · MIME

받고 싶은 형식을 적는 요청 헤더

Accept · Accept-Charset · Accept-Language · Accept-Encoding · 콘텐츠 협상 · Vary

본문과 나란히 붙는 다른 표현 헤더

Content-Length · Content-Encoding · Content-Language · Content-Disposition · Content-Location · Transfer-Encoding

형식이 안 맞을 때 돌아오는 상태 코드

HTTP 415 · HTTP 406 · HTTP 400 · 상태 코드

이 값을 붙여 본문을 보내는 요청 메서드

POST · PUT · PATCH · 요청 본문

이 값이 가리키는 본문 형식

HTML · JSON · XML · CSV · UTF-8 · multipart-form-data · application-x-www-form-urlencoded · application-octet-stream · application-gzip

이 값을 정하는 폼 전송 장치

HTML 폼 · enctype · 파일 업로드 · quoted-printable · CRLF

압축과 이 값을 가르는 코딩

gzip · 압축 · 콘텐츠 코딩 · Content-Encoding

이 값 대신 내용을 짐작할 때 터지는 문제

MIME 스니핑 · X-Content-Type-Options · 교차 사이트 스크립팅 · 권한 상승 · 매직 넘버 · 파일 시그니처 · 문자 깨짐

이 값을 해석하는 주체

사용자 에이전트 · 브라우저 · 오리진 서버 · 클라이언트 · 서버

이 필드를 정의하는 표준 문서

RFC 9110 · RFC 7231 · RFC 2616 · RFC 7578 · RFC 6713 · RFC 2046 · RFC 6838 · RFC 2119 · MIME Sniffing Standard · Fetch Standard · HTML Living Standard

이 표준을 내는 기관과 등록부

IETF · WHATWG · IANA · IANA 미디어 타입 레지스트리 · HTTP 헤더 필드 레지스트리

다른 이름: Content-Type 헤더 · Content-Type 필드 · 콘텐츠 타입