require "spec"
require "http/content"

describe HTTP::ChunkedContent do
  it "delays reading the next chunk as soon as one is consumed (#3270)" do
    mem = IO::Memory.new("4\r\n123\n\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)
    bytes = uninitialized UInt8[4]
    bytes_read = content.read(bytes.to_slice)
    bytes_read.should eq(4)
    String.new(bytes.to_slice).should eq("123\n")
    mem.pos.should eq(7) # only this chunk was read
  end

  it "peeks" do
    mem = IO::Memory.new("4\r\n123\n\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.peek.should eq("123\n".to_slice)
    content.skip(4)
    content.peek.should eq Bytes.empty
  end

  it "peeks into next chunk" do
    mem = IO::Memory.new("4\r\n123\n\r\n3\r\n456\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(4)
    content.peek.should eq("456".to_slice)
    content.gets_to_end.should eq("456")
  end

  it "skips" do
    mem = IO::Memory.new("4\r\n123\n\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(2)
    content.read_char.should eq('3')

    expect_raises(IO::EOFError) do
      content.skip(10)
    end
  end

  it "skips (2)" do
    mem = IO::Memory.new("4\r\n123\n\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(2)
    content.gets_to_end.should eq("3\n")
  end

  it "skips (3)" do
    mem = IO::Memory.new("4\r\n123\n\r\n3\r\n456\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(4)
    content.gets_to_end.should eq("456")
  end

  it "#gets reads multiple chunks" do
    mem = IO::Memory.new("1\r\nA\r\n1\r\nB\r\n0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.gets.should eq "AB"
    mem.pos.should eq mem.bytesize
  end

  it "#gets reads multiple chunks with \n" do
    # The RFC format requires CRLF, but several standard implementations also
    # accept LF, so we should too.
    mem = IO::Memory.new("1\nA\n1\nB\n0\n\n")
    content = HTTP::ChunkedContent.new(mem)

    content.gets.should eq "AB"
    mem.pos.should eq mem.bytesize
  end

  it "#read reads empty content" do
    mem = IO::Memory.new("0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.read(Bytes.new(5)).should eq 0
    mem.pos.should eq mem.bytesize
  end

  it "#read_byte reads empty content" do
    mem = IO::Memory.new("0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.read_byte.should be_nil
    mem.pos.should eq mem.bytesize
  end

  it "#peek reads empty content" do
    mem = IO::Memory.new("0\r\n\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.peek.should eq Bytes.empty
    mem.pos.should eq mem.bytesize
  end

  it "#read handles interrupted io" do
    mem = IO::Memory.new("3\r\n123\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(3)
    expect_raises IO::EOFError do
      content.read Bytes.new(5)
    end
  end

  it "#read_byte handles interrupted io" do
    mem = IO::Memory.new("3\r\n123\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(3)
    expect_raises IO::EOFError do
      content.read_byte
    end
  end

  it "#peek handles interrupted io" do
    mem = IO::Memory.new("3\r\n123\r\n")
    content = HTTP::ChunkedContent.new(mem)

    content.skip(3)
    expect_raises IO::EOFError do
      content.peek
    end
  end

  it "#read handles empty data" do
    mem = IO::Memory.new("3\r\n")
    content = HTTP::ChunkedContent.new(mem)

    expect_raises IO::EOFError do
      content.read Bytes.new(1)
    end
  end

  it "#read_byte handles empty data" do
    mem = IO::Memory.new("3\r\n")
    content = HTTP::ChunkedContent.new(mem)

    expect_raises IO::Error do
      content.read_byte
    end
  end

  it "#peek handles empty data" do
    mem = IO::Memory.new("3\r\n")
    content = HTTP::ChunkedContent.new(mem)

    expect_raises IO::EOFError do
      content.peek
    end
  end

  it "handles empty io" do
    mem = IO::Memory.new("")
    chunked = HTTP::ChunkedContent.new(mem)
    expect_raises IO::EOFError do
      chunked.gets
    end
  end

  it "reads chunk extensions" do
    mem = IO::Memory.new(%(1;progress=50;foo="bar"\r\nA\r\n1;progress=100\r\nB\r\n0\r\n\r\n"))

    chunked = HTTP::ChunkedContent.new(mem)
    chunked.gets(2).should eq "AB"
  end

  it "reads chunked trailer part" do
    mem = IO::Memory.new("0\r\nAdditional-Header: Foo\r\n\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    chunked.gets.should be_nil
    mem.gets.should be_nil
    chunked.headers.should eq HTTP::Headers{"Additional-Header" => "Foo"}
  end

  it "fails if unterminated chunked trailer part" do
    mem = IO::Memory.new("0\r\nAdditional-Header: Foo")

    chunked = HTTP::ChunkedContent.new(mem)
    expect_raises IO::EOFError do
      chunked.gets
    end
  end

  describe "long trailer part" do
    it "fails for long single header" do
      mem = IO::Memory.new("0\r\nFoo: Bar Baz Qux\r\n\r\n")

      chunked = HTTP::ChunkedContent.new(mem, max_headers_size: 12)
      expect_raises(IO::Error, "Trailing headers too long") do
        chunked.gets
      end
      chunked.headers.should be_empty
    end

    it "fails for long combined headers" do
      mem = IO::Memory.new("0\r\nFoo: Bar\r\nBaz: Qux\r\n\r\n")

      chunked = HTTP::ChunkedContent.new(mem, max_headers_size: 12)
      expect_raises(IO::Error, "Trailing headers too long") do
        chunked.gets
      end
      chunked.headers.should eq HTTP::Headers{"Foo" => "Bar"}
    end
  end

  it "fails if not properly delimited" do
    mem = IO::Memory.new("0\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    expect_raises IO::EOFError do
      chunked.gets
    end
  end

  it "fails if not properly delimited" do
    mem = IO::Memory.new("1\r\nA\r\n0\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    expect_raises IO::EOFError do
      chunked.gets
    end
  end

  it "fails if invalid chunk size" do
    mem = IO::Memory.new("G\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    expect_raises IO::Error, "Invalid HTTP chunked content: invalid chunk size" do
      chunked.gets
    end
  end

  it "#read stops reading after final chunk" do
    mem = IO::Memory.new("0\r\n\r\n1\r\nA\r\n0\r\n\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    chunked.read(Bytes.new(8)).should eq 0
    mem.pos.should eq 5
    chunked.read(Bytes.new(8)).should eq 0
    mem.pos.should eq 5
  end

  it "#read_byte stops reading after final chunk" do
    mem = IO::Memory.new("0\r\n\r\n1\r\nA\r\n0\r\n\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    chunked.read_byte.should be_nil
    mem.pos.should eq 5
    chunked.read_byte.should be_nil
    mem.pos.should eq 5
  end

  it "#peek stops reading after final chunk" do
    mem = IO::Memory.new("0\r\n\r\n1\r\nA\r\n0\r\n\r\n")

    chunked = HTTP::ChunkedContent.new(mem)
    chunked.peek.should eq Bytes.empty
    mem.pos.should eq 5
    chunked.peek.should eq Bytes.empty
    mem.pos.should eq 5
  end
end
