API-Docker

 view release on metacpan or  search on metacpan

lib/API/Docker/Role/HTTP.pm  view on Meta::CPAN

      . 'terminating zero chunk')
    unless defined $line;

  $self->_croak_truncated($ctx, phase => 'chunk-header',
    detail => 'the stream ended inside a chunk header, after '
      . length($line) . ' byte' . (length($line) == 1 ? '' : 's') . ' of one')
    unless $line =~ /\n\z/;

  # The line arrived whole and terminated, and its size is not a hexadecimal
  # number. Left to hex() that warns once and reads as 0 -- the terminating
  # zero chunk -- so a 200 whose framing is corrupt used to come back as an
  # empty body, the same body-shaped lie a truncation is. A chunk size is hex
  # digits, optionally followed by a ';' extension (RFC 9112 section 7.1.1),
  # which is read past and discarded; anything else is refused here rather than
  # silently misread by the caller. The returned size is parsed from the same
  # match, so hex() is called on nothing but hex digits and never has cause to
  # warn on a legal extension either.
  my ($size) = $line =~ /^([0-9A-Fa-f]+)(?:;.*)?\r?\n\z/;
  $self->_croak_truncated($ctx, phase => 'chunk-header',
    detail => "the chunk size line '" . ($line =~ s/\r?\n\z//r)
      . "' is not a hexadecimal number")
    unless defined $size;

  return hex($size);
}

# Completeness only, never content: a terminator that arrived but is not CRLF
# is a daemon speaking chunked wrongly, which is a different complaint and one
# this reader has never made.
sub _assert_chunk_terminator {
  my ($self, $ctx, $line) = @_;

  $self->_croak_truncated($ctx, phase => 'chunk-terminator',
    detail => 'the stream ended before the CRLF that terminates a chunk')
    unless defined $line && $line =~ /\n\z/;

  return;
}

# The declared body length, validated to be the digits RFC 9110 section 8.6
# requires before it is compared against or counted down (karr k113). The
# sibling of _assert_chunk_header's hex check: a Content-Length that is not a
# number -- 'abc', an empty value, a duplicated '11, 11', a leading space --
# left as it stood is run through `$len > 0`, which warns once ("isn't
# numeric") and reads as 0, so the body is taken to be empty and a response
# that had one comes back blank. Refused here rather than silently misread, so
# hex()'s sibling warning is never reached either.
sub _assert_content_length {
  my ($self, $ctx, $value) = @_;

  return $value if $value =~ /\A[0-9]+\z/;

  $self->_croak_truncated($ctx, phase => 'content-length',
    detail => "the Content-Length header '" . $value . "' is not a number");
}

sub _uri_encode {
  my ($str) = @_;
  # Escape a character string by its UTF-8 bytes ('ü' -> %C3%BC, not %FC), and
  # a byte string as it stands. ord() on a character is not its wire byte: a
  # name or tag typed under `use utf8`, or read through a :utf8 layer, arrives
  # as characters and used to escape to a lone high byte or a bare codepoint
  # (%FC, %4E2D) that is not UTF-8 at all. But the encoding cannot be
  # unconditional: encode_json has already handed a HASH param (filters among
  # them) its UTF-8 octets, and re-encoding those would double them
  # (%C3%BC -> %C3%83%C2%BC). The utf8 flag is exactly that distinction -- on
  # for a decoded string, off for encode_json's output -- so a copy is encoded
  # only when it carries one, leaving the caller's own value untouched either
  # way.
  my $bytes = $str;
  utf8::encode($bytes) if utf8::is_utf8($bytes);
  $bytes =~ s/([^A-Za-z0-9\-_.~:\/])/sprintf("%%%02X", ord($1))/ge;
  return $bytes;
}

sub get {
  my ($self, $path, %opts) = @_;
  return $self->_request('GET', $path, %opts);
}


sub post {
  my ($self, $path, $body, %opts) = @_;
  $opts{body} = $body if defined $body;
  return $self->_request('POST', $path, %opts);
}


sub put {
  my ($self, $path, $body, %opts) = @_;
  $opts{body} = $body if defined $body;
  return $self->_request('PUT', $path, %opts);
}


sub delete_request {
  my ($self, $path, %opts) = @_;
  return $self->_request('DELETE', $path, %opts);
}


sub head {
  my ($self, $path, %opts) = @_;
  return $self->_request('HEAD', $path, %opts);
}


sub stream_frames {
  my ($self, $method, $path, %opts) = @_;

  my $tty = delete $opts{tty};

  if (my $cb = delete $opts{on_frame}) {
    # tty is a declaration here, not the hint it is on the buffered path. The
    # sniff below needs the whole body to decide, and the whole body is what a
    # callback stream does not have; so an unframed stream has to say so, and
    # anything not declared is required to be framed.
    return $self->_request($method, $path, %opts,
      $tty
        ? ( on_chunk => sub { $cb->({ stream => 'raw', data => $_[0] }, $_[1]) } )
        : ( on_frame => $cb ),
    );
  }

  my $body = $self->_request($method, $path, %opts, raw => 1);

  return [] unless defined $body && length $body;

  my $frames = $tty ? undef : $self->_demux_frames($body);

  return $frames if $frames;



( run in 0.725 second using v1.01-cache-2.11-cpan-364913b4093 )