<commit_before>use Mojo::Base -strict;

BEGIN {
  $ENV{MOJO_NO_IPV6} = 1;
  $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}

use Test::More;
use Mojo::IOLoop::Client;
use Mojo::UserAgent;

plan skip_all => 'set TEST_SOCKS to enable this test (developer only!)'
  unless $ENV{TEST_SOCKS};
plan skip_all => 'IO::Socket::SOCKS 0.63 required for this test!'
  unless Mojo::IOLoop::Client::SOCKS;

use Mojo::IOLoop;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::Stream;

my $port   = Mojo::IOLoop::Server->generate_port;
my $server = IO::Socket::Socks->new(
  Blocking    => 0,
  Listen      => 10,
  ProxyAddr   => '127.0.0.1',
  ProxyPort   => $port,
  RequireAuth => 1,
  UserAuth    => sub { $_[0] eq 'foo' && $_[1] eq 'bar' }
);

Mojo::IOLoop->singleton->reactor->io(
  $server => sub {
    my $reactor = shift;
    my $client  = $server->accept;
    $client->blocking(0);
    my $done;
    $reactor->io(
      $client => sub {
        my $reactor = shift;
        my $err     = $IO::Socket::Socks::SOCKS_ERROR;
        if ($client->ready) {
          if ($done) {
            $reactor->remove($client);
            my $stream = Mojo::IOLoop::Stream->new($client);
            my $buffer = '';
            Mojo::IOLoop->stream($stream);
            $stream->on(
              read => sub {
                my ($stream, $chunk) = @_;
                $buffer .= $chunk;
                my $response = "HTTP/1.1 200 OK\x0d\x0a"
                  . "Content-Length: 3\x0d\x0a\x0d\x0aHi!";
                $stream->write($response => sub { shift->close })
                  if $buffer =~ /\x0d\x0a\x0d\x0a/;
              }
            );
          }
          else {
            my $command = $client->command;
            $client->command_reply(IO::Socket::Socks::REPLY_SUCCESS(),
              $command->[1], $command->[2]);
            $done = 1;
          }
        }
        elsif ($err == IO::Socket::Socks::SOCKS_WANT_WRITE()) {
          $reactor->watch($client, 1, 1);
        }
        elsif ($err == IO::Socket::Socks::SOCKS_WANT_READ()) {
          $reactor->watch($client, 1, 0);
        }
      }
    );
  }
);

my $ua = Mojo::UserAgent->new(ioloop => Mojo::IOLoop->singleton);
$ua->proxy->http("socks://foo:bar\@127.0.0.1:$port");
my $tx = $ua->get('http://127.0.0.1:3000/');
is $tx->res->code, 200,   'right status';
is $tx->res->body, 'Hi!', 'right content';
<commit_msg>much more comprehensive SOCKS tests<commit_after>use Mojo::Base -strict;

BEGIN {
  $ENV{MOJO_NO_IPV6} = 1;
  $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll';
}

use Test::More;
use Mojo::IOLoop::Client;

plan skip_all => 'set TEST_SOCKS to enable this test (developer only!)'
  unless $ENV{TEST_SOCKS};
plan skip_all => 'IO::Socket::SOCKS 0.63 required for this test!'
  unless Mojo::IOLoop::Client::SOCKS;
plan skip_all => 'IO::Socket::SSL 1.84 required for this test!'
  unless Mojo::IOLoop::Server::TLS;

use Mojo::IOLoop;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::Stream;
use Mojo::UserAgent;
use Mojolicious::Lite;
use Scalar::Util 'weaken';

get '/' => sub {
  my $c = shift;
  $c->render(text => $c->tx->remote_port);
};

websocket '/echo' => sub {
  my $c = shift;
  $c->on(
    message => sub {
      my $c = shift;
      $c->send($c->tx->remote_port);
    }
  );
};

get '/secure' => sub {
  my $c = shift;
  $c->render(
    text => $c->req->url->to_abs->protocol . ':' . $c->tx->remote_port);
};

my $port   = Mojo::IOLoop::Server->generate_port;
my $server = IO::Socket::Socks->new(
  Blocking    => 0,
  Listen      => 10,
  ProxyAddr   => '127.0.0.1',
  ProxyPort   => $port,
  RequireAuth => 1,
  UserAuth    => sub { $_[0] eq 'foo' && $_[1] eq 'bar' }
);

# SOCKS proxy server for testing
my $last;
Mojo::IOLoop->singleton->reactor->io(
  $server => sub {
    my $reactor = shift;

    my $client = $server->accept;
    $client->blocking(0);
    my ($address, $port);
    $reactor->io(
      $client => sub {
        my $reactor = shift;

        my $err = $IO::Socket::Socks::SOCKS_ERROR;
        if ($client->ready) {

          if ($address) {
            $reactor->remove($client);
            Mojo::IOLoop->client(
              {address => $address, port => $port} => sub {
                my ($loop, $err, $server) = @_;
                $last = $server->handle->sockport;
                weaken $server;
                $client = Mojo::IOLoop::Stream->new($client);
                Mojo::IOLoop->stream($client);
                $client->on(read  => sub { $server->write(pop) });
                $client->on(close => sub { $server->close });
                $server->on(read  => sub { $client->write(pop) });
                $server->on(close => sub { $client->close });
              }
            );
          }

          else {
            ($address, $port) = @{$client->command}[1, 2];
            $client->command_reply(IO::Socket::Socks::REPLY_SUCCESS(),
              $address, $port);
          }
        }
        elsif ($err == IO::Socket::Socks::SOCKS_WANT_WRITE()) {
          $reactor->watch($client, 1, 1);
        }
        elsif ($err == IO::Socket::Socks::SOCKS_WANT_READ()) {
          $reactor->watch($client, 1, 0);
        }
      }
    );
  }
);

# Simple request with SOCKS proxy
my $ua = Mojo::UserAgent->new(ioloop => Mojo::IOLoop->singleton);
$ua->proxy->http("socks://foo:bar\@127.0.0.1:$port");
my $tx = $ua->get('/');
ok $tx->success, 'successful';
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, $last, 'right content';
isnt $last, $port, 'different port';

# Keep alive request with SOCKS proxy
$tx = $ua->get('/');
ok $tx->success,    'successful';
ok $tx->kept_alive, 'kept connection alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200, 'right status';
is $tx->res->body, $last, 'right content';
isnt $last, $port, 'different port';

# WebSocket with SOCKS proxy
my $result;
$ua->websocket(
  '/echo' => sub {
    my ($ua, $tx) = @_;
    $tx->on(
      message => sub {
        $result = pop;
        Mojo::IOLoop->stop;
      }
    );
    $tx->send('test');
  }
);
Mojo::IOLoop->start;
is $result, $last, 'right result';
isnt $last, $port, 'different port';

# HTTPS request with SOCKS proxy
$ua->proxy->https("socks://foo:bar\@127.0.0.1:$port");
$ua->server->url('https');
$tx = $ua->get('/secure');
ok $tx->success, 'successful';
ok !$tx->kept_alive, 'kept connection not alive';
ok $tx->keep_alive, 'keep connection alive';
is $tx->res->code, 200,           'right status';
is $tx->res->body, "https:$last", 'right content';
isnt $last, $port, 'different port';

{'fraction_non_alphanumeric': 0.19963811821471653, 'fraction_numerical': 0.016284680337756333, 'mean_word_length': 2.533830580713905, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 151, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 6, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6169291', 'n_tokens_mistral': 2504, 'n_tokens_neox': 2389, 'n_words': 502}