Created
October 8, 2025 00:18
-
-
Save xeioex/7fb014eb3ef5a3a2fb8768b30705696a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/perl | |
| # (C) Dmitry Volyntsev | |
| # (C) Nginx, Inc. | |
| # Tests for http njs module, fetch method with HTTPS through forward proxy. | |
| ############################################################################### | |
| use warnings; | |
| use strict; | |
| use Test::More; | |
| use Socket qw/ CRLF SOCK_STREAM /; | |
| use IO::Select; | |
| BEGIN { use FindBin; chdir($FindBin::Bin); } | |
| use lib 'lib'; | |
| use Test::Nginx; | |
| ############################################################################### | |
| select STDERR; $| = 1; | |
| select STDOUT; $| = 1; | |
| my $t = Test::Nginx->new()->has(qw/http http_ssl/) | |
| ->write_file_expand('nginx.conf', <<'EOF'); | |
| %%TEST_GLOBALS%% | |
| daemon off; | |
| events { | |
| } | |
| http { | |
| %%TEST_GLOBALS_HTTP%% | |
| js_import test.js; | |
| server { | |
| listen 127.0.0.1:8080; | |
| server_name localhost; | |
| location /https_via_proxy { | |
| js_fetch_proxy http://127.0.0.1:%%PORT_8082%%; | |
| js_fetch_proxy_auth_basic testuser testpass; | |
| js_content test.https_fetch; | |
| } | |
| location /https_no_proxy { | |
| js_content test.https_fetch; | |
| } | |
| location /https_via_proxy_status { | |
| js_fetch_proxy http://127.0.0.1:%%PORT_8082%%; | |
| js_fetch_proxy_auth_basic testuser testpass; | |
| js_content test.https_fetch_status; | |
| } | |
| } | |
| server { | |
| listen 127.0.0.1:%%PORT_8083%% ssl; | |
| server_name localhost; | |
| ssl_certificate localhost.crt; | |
| ssl_certificate_key localhost.key; | |
| location = /test { | |
| return 200 "ORIGIN:HTTPS:response"; | |
| } | |
| } | |
| } | |
| EOF | |
| my $p2 = port(8082); | |
| my $p3 = port(8083); | |
| $t->write_file('test.js', <<EOF); | |
| async function https_fetch(r) { | |
| try { | |
| let reply = await ngx.fetch('https://127.0.0.1:$p3/test', | |
| {verify: false}); | |
| let body = await reply.text(); | |
| r.return(200, body); | |
| } catch (e) { | |
| r.return(500, e.message); | |
| } | |
| } | |
| async function https_fetch_status(r) { | |
| try { | |
| let reply = await ngx.fetch('https://127.0.0.1:$p3/test', | |
| {verify: false}); | |
| r.return(200, 'STATUS:' + reply.status); | |
| } catch (e) { | |
| r.return(500, e.message); | |
| } | |
| } | |
| export default {https_fetch, https_fetch_status}; | |
| EOF | |
| $t->write_file('openssl.conf', <<EOF); | |
| [ req ] | |
| default_bits = 2048 | |
| encrypt_key = no | |
| distinguished_name = req_distinguished_name | |
| [ req_distinguished_name ] | |
| EOF | |
| my $d = $t->testdir(); | |
| system('openssl req -x509 -new ' | |
| . "-config $d/openssl.conf -subj /CN=localhost/ " | |
| . "-out $d/localhost.crt -keyout $d/localhost.key " | |
| . ">>$d/openssl.out 2>&1") == 0 | |
| or die "Can't create certificate: $!\n"; | |
| $t->try_run('no njs.fetch')->plan(1); | |
| $t->run_daemon(\&https_proxy_daemon, $p2, $p3); | |
| $t->waitforsocket('127.0.0.1:' . $p2); | |
| ############################################################################### | |
| my $resp = http_get('/https_via_proxy'); | |
| like($resp, qr/PROXY:method=CONNECT/, 'proxy received CONNECT method'); | |
| ############################################################################### | |
| sub https_proxy_daemon { | |
| my ($port, $origin_port) = @_; | |
| my $server = IO::Socket::INET->new( | |
| Proto => 'tcp', | |
| LocalAddr => "127.0.0.1:$port", | |
| Listen => 5, | |
| Reuse => 1 | |
| ) or die "Can't create listening socket: $!\n"; | |
| local $SIG{PIPE} = 'IGNORE'; | |
| while (my $client = $server->accept()) { | |
| $client->autoflush(1); | |
| my $headers = ''; | |
| my $uri = ''; | |
| my $method = ''; | |
| my $proxy_auth = ''; | |
| my $first_line = 1; | |
| while (<$client>) { | |
| $headers .= $_; | |
| last if (/^\x0d?\x0a?$/); | |
| if ($first_line) { | |
| $method = $1 if /^(\S+)\s+/; | |
| $uri = $1 if /^\S+\s+(\S+)\s+HTTP/; | |
| $first_line = 0; | |
| } | |
| $proxy_auth = $1 if /^Proxy-Authorization:\s*(.+?)\s*$/i; | |
| } | |
| if ($method eq 'CONNECT') { | |
| if ($proxy_auth =~ /^Basic\s+dGVzdHVzZXI6dGVzdHBhc3M=$/i) { | |
| print $client "HTTP/1.1 200 Connection established" . CRLF . | |
| CRLF; | |
| my $origin = IO::Socket::INET->new( | |
| PeerAddr => "127.0.0.1:$origin_port", | |
| Proto => 'tcp', | |
| Type => SOCK_STREAM | |
| ) or do { | |
| close $client; | |
| next; | |
| }; | |
| my $sel = IO::Select->new($client, $origin); | |
| while (1) { | |
| my @ready = $sel->can_read(3.0); | |
| last unless @ready; | |
| foreach my $sock (@ready) { | |
| my $buf; | |
| my $n = sysread($sock, $buf, 4096); | |
| if (!defined($n) || $n == 0) { | |
| $sel->remove($client, $origin); | |
| close $origin; | |
| close $client; | |
| last; | |
| } | |
| if ($sock == $client) { | |
| syswrite($origin, $buf); | |
| } else { | |
| syswrite($client, $buf); | |
| } | |
| } | |
| } | |
| } else { | |
| print $client | |
| "HTTP/1.1 407 Proxy Authentication Required" . CRLF . | |
| "Proxy-Authenticate: Basic realm=\"proxy\"" . CRLF . | |
| "Content-Length: 0" . CRLF . | |
| "Connection: close" . CRLF . CRLF; | |
| } | |
| } | |
| close $client; | |
| } | |
| } | |
| ############################################################################### |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment