request.ex (2224B)
1 defmodule Mint.HTTP1.Request do 2 @moduledoc false 3 4 import Mint.HTTP1.Parse 5 6 def encode(method, target, headers, body) do 7 body = [ 8 encode_request_line(method, target), 9 encode_headers(headers), 10 "\r\n", 11 encode_body(body) 12 ] 13 14 {:ok, body} 15 catch 16 {:mint, reason} -> {:error, reason} 17 end 18 19 defp encode_request_line(method, target) do 20 validate_target!(target) 21 [method, ?\s, target, " HTTP/1.1\r\n"] 22 end 23 24 defp encode_headers(headers) do 25 Enum.reduce(headers, "", fn {name, value}, acc -> 26 validate_header_name!(name) 27 validate_header_value!(name, value) 28 [acc, name, ": ", value, "\r\n"] 29 end) 30 end 31 32 defp encode_body(nil), do: "" 33 defp encode_body(:stream), do: "" 34 defp encode_body(body), do: body 35 36 def encode_chunk(:eof) do 37 "0\r\n\r\n" 38 end 39 40 def encode_chunk({:eof, trailing_headers}) do 41 ["0\r\n", encode_headers(trailing_headers), "\r\n"] 42 end 43 44 def encode_chunk(chunk) do 45 length = IO.iodata_length(chunk) 46 [Integer.to_string(length, 16), "\r\n", chunk, "\r\n"] 47 end 48 49 # Percent-encoding is not case sensitive so we have to account for lowercase and uppercase. 50 @hex_characters '0123456789abcdefABCDEF' 51 52 defp validate_target!(target), do: validate_target!(target, target) 53 54 defp validate_target!(<<?%, char1, char2, rest::binary>>, original_target) 55 when char1 in @hex_characters and char2 in @hex_characters do 56 validate_target!(rest, original_target) 57 end 58 59 defp validate_target!(<<char, rest::binary>>, original_target) do 60 if URI.char_unescaped?(char) do 61 validate_target!(rest, original_target) 62 else 63 throw({:mint, {:invalid_request_target, original_target}}) 64 end 65 end 66 67 defp validate_target!(<<>>, _original_target) do 68 :ok 69 end 70 71 defp validate_header_name!(name) do 72 _ = 73 for <<char <- name>> do 74 unless is_tchar(char) do 75 throw({:mint, {:invalid_header_name, name}}) 76 end 77 end 78 79 :ok 80 end 81 82 defp validate_header_value!(name, value) do 83 _ = 84 for <<char <- value>> do 85 unless is_vchar(char) or char in '\s\t' do 86 throw({:mint, {:invalid_header_value, name, value}}) 87 end 88 end 89 90 :ok 91 end 92 end