GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

BrowserBundleTests.pm 30.8 KB
Newer Older
1
package TBBTestSuite::TestSuite::BrowserBundleTests;
2 3 4

use warnings;
use strict;
boklm's avatar
boklm committed
5 6 7

use parent 'TBBTestSuite::TestSuite';

8 9 10 11 12 13 14
use English;
use FindBin;
use File::Slurp;
use File::Spec;
use File::Find;
use File::Type;
use File::Copy;
15
use File::Temp;
16 17 18
use JSON;
use Digest::SHA qw(sha256_hex);
use LWP::UserAgent;
19
use TBBTestSuite::Common qw(exit_error winpath clone_strip_coderef screenshot_thumbnail);
20 21
use TBBTestSuite::Options qw($options);
use TBBTestSuite::Tests::VirusTotal qw(virustotal_run);
22
use TBBTestSuite::Tests::Command qw(command_run);
23
use TBBTestSuite::Tests::TorBootstrap;
boklm's avatar
boklm committed
24
use TBBTestSuite::XServer qw(start_X stop_X set_Xmode);
25

boklm's avatar
boklm committed
26 27 28
sub test_types {
    return {
        tor_bootstrap => \&TBBTestSuite::Tests::TorBootstrap::start_tor,
29
        marionette    => \&marionette_run,
boklm's avatar
boklm committed
30 31 32 33
        virustotal    => \&virustotal_run,
        command       => \&command_run,
    };
}
34

boklm's avatar
boklm committed
35 36 37
sub type {
    'browserbundle';
}
38

boklm's avatar
boklm committed
39 40 41
sub description {
    'Tor Browser Bundle integration tests';
}
42

43 44
our @tests = (
    {
boklm's avatar
boklm committed
45 46 47 48 49 50 51 52 53
        name            => 'readelf_RELRO',
        fail_type       => 'warning',
        type            => 'command',
        descr           => 'Check if binaries are RELocation Read-Only',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-ld' ],
        check_output    => sub { ( $_[0] =~ m/GNU_RELRO/ )
                                 && ( $_[0] =~ m/BIND_NOW/ ) },
        enable          => sub { $OSNAME eq 'linux' },
54 55
    },
    {
boklm's avatar
boklm committed
56 57 58 59 60 61 62 63
        name            => 'readelf_stack_canary',
        fail_type       => 'warning',
        type            => 'command',
        descr           => 'Check for stack canary support',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-s' ],
        check_output    => sub { $_[0] =~ m/__stack_chk_fail/ },
        enable          => sub { $OSNAME eq 'linux' },
64 65
    },
    {
boklm's avatar
boklm committed
66 67 68 69 70 71 72
        name            => 'readelf_NX',
        type            => 'command',
        descr           => 'Check for NX support',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-W', '-l' ],
        check_output    => sub { ! ($_[0] =~ m/GNU_STACK.+RWE/) },
        enable          => sub { $OSNAME eq 'linux' },
73 74
    },
    {
boklm's avatar
boklm committed
75 76 77 78 79 80 81
        name            => 'readelf_PIE',
        type            => 'command',
        descr           => 'Check for PIE support',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-h' ],
        check_output    => sub { $_[0] =~ m/Type:\s+DYN/ },
        enable          => sub { $OSNAME eq 'linux' },
82 83
    },
    {
boklm's avatar
boklm committed
84 85 86 87 88 89 90 91
        name            => 'readelf_no_rpath',
        fail_type       => 'warning',
        type            => 'command',
        descr           => 'Check for no rpath',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-d' ],
        check_output    => sub { ! ( $_[0] =~ m/RPATH/ ) },
        enable          => sub { $OSNAME eq 'linux' },
92 93
    },
    {
boklm's avatar
boklm committed
94 95 96 97 98 99 100
        name            => 'readelf_no_runpath',
        type            => 'command',
        descr           => 'Check for no runpath',
        files           => \&tbb_binfiles,
        command         => [ 'readelf', '-d' ],
        check_output    => sub { ! ( $_[0] =~ m/runpath/ ) },
        enable          => sub { $OSNAME eq 'linux' },
101 102
    },
    {
boklm's avatar
boklm committed
103 104 105 106 107
        name            => 'tor_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
108
        run_once        => 1,
109 110
    },
    {
boklm's avatar
boklm committed
111 112 113
        name            => 'tor_bridge',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using a bridge',
114
        enable          => sub { $OSNAME eq 'linux' },
115
        run_once        => 1,
116 117
    },
    {
boklm's avatar
boklm committed
118 119 120 121
        name            => 'tor_bridge_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using a bridge and an http proxy',
        httpproxy       => 1,
122
        enable          => sub { $OSNAME eq 'linux' },
123
        run_once        => 1,
124 125
    },
    {
boklm's avatar
boklm committed
126 127 128 129
        name            => 'tor_obfs3',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs3',
        enable          => sub { $OSNAME eq 'linux' },
130
        run_once        => 1,
131 132
    },
    {
boklm's avatar
boklm committed
133 134 135 136 137
        name            => 'tor_obfs3_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs3 and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
138
        run_once        => 1,
139
    },
140
    {
boklm's avatar
boklm committed
141 142 143 144
        name            => 'tor_obfs4',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs4',
        enable          => sub { $OSNAME eq 'linux' && $_[0]->{version} !~ m/^4.0/ },
145
        run_once        => 1,
146 147
    },
    {
boklm's avatar
boklm committed
148 149 150 151 152
        name            => 'tor_obfs4_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using obfs4 and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' && $_[0]->{version} !~ m/^4.0/ },
153
        run_once        => 1,
154
    },
155
    {
boklm's avatar
boklm committed
156 157 158 159
        name            => 'tor_fte',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using fteproxy',
        enable          => sub { $OSNAME eq 'linux' },
160
        run_once        => 1,
161 162
    },
    {
boklm's avatar
boklm committed
163 164 165 166 167
        name            => 'tor_fte_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using fteproxy and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
168
        run_once        => 1,
169
    },
170 171 172 173 174
    {
        name            => 'tor_scramblesuit',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using scramblesuit',
        enable          => sub { $OSNAME eq 'linux' },
175
        run_once        => 1,
176 177 178 179 180 181 182
    },
    {
        name            => 'tor_scramblesuit_httpproxy',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using scramblesuit and an http proxy',
        httpproxy       => 1,
        enable          => sub { $OSNAME eq 'linux' },
183
        run_once        => 1,
184
    },
185 186 187 188 189
    {
        name            => 'tor_meek-amazon',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using meek-amazon',
        enable          => sub { $OSNAME eq 'linux' },
190
        run_once        => 1,
191 192 193 194 195 196
    },
    {
        name            => 'tor_meek-azure',
        type            => 'tor_bootstrap',
        descr           => 'Access tor using meek-azure',
        enable          => sub { $OSNAME eq 'linux' },
197
        run_once        => 1,
198
    },
199
    {
boklm's avatar
boklm committed
200 201 202 203 204
        name            => 'tor_bootstrap',
        type            => 'tor_bootstrap',
        descr           => 'Check that we can bootstrap tor',
        fail_type       => 'fatal',
        no_kill         => 1,
205 206 207
        use_default_config => 1,
    },
    {
boklm's avatar
boklm committed
208
        name            => 'screenshots',
209
        type            => 'marionette',
boklm's avatar
boklm committed
210
        descr           => 'Take some screenshots',
211 212
    },
    {
boklm's avatar
boklm committed
213
        name            => 'check',
214
        type            => 'marionette',
boklm's avatar
boklm committed
215 216
        use_net         => 1,
        descr           => 'Check that http://check.torproject.org/ think we are using tor',
217 218
    },
    {
boklm's avatar
boklm committed
219
        name            => 'https-everywhere',
220
        type            => 'marionette',
boklm's avatar
boklm committed
221 222
        use_net         => 1,
        descr           => 'Check that https everywhere is enabled and working',
223 224
    },
    {
boklm's avatar
boklm committed
225
        name            => 'https-everywhere-disabled',
226 227
        marionette_test => 'https-everywhere',
        type            => 'marionette',
boklm's avatar
boklm committed
228 229 230 231
        descr           => 'Check that https everywhere is not doing anything when disabled',
        use_net         => 1,
        pre             => sub { toggle_https_everywhere($_[0], 0) },
        post            => sub { toggle_https_everywhere($_[0], 1) },
232 233
    },
    {
boklm's avatar
boklm committed
234
        name            => 'settings',
235
        type            => 'marionette',
boklm's avatar
boklm committed
236
        descr           => 'Check that some important settings are correctly set',
237
    },
boklm's avatar
boklm committed
238
    {
boklm's avatar
boklm committed
239
        name            => 'acid3',
boklm's avatar
boklm committed
240
        type            => 'marionette',
boklm's avatar
boklm committed
241 242
        descr           => 'acid3 tests',
        use_net         => 1,
boklm's avatar
boklm committed
243
        retry           => 4,
boklm's avatar
boklm committed
244
    },
boklm's avatar
boklm committed
245
    {
boklm's avatar
boklm committed
246
        name            => 'slider_settings_1',
247 248
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
249 250 251 252 253
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 1,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
254 255
    },
    {
boklm's avatar
boklm committed
256
        name            => 'slider_settings_2',
257 258
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
259 260 261 262 263
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 2,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
264 265
    },
    {
boklm's avatar
boklm committed
266
        name            => 'slider_settings_3',
267 268
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
269 270 271 272 273
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 3,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
274 275
    },
    {
boklm's avatar
boklm committed
276
        name            => 'slider_settings_4',
277 278
        marionette_test => 'slider_settings',
        type            => 'marionette',
boklm's avatar
boklm committed
279 280 281 282 283
        descr           => 'Check that settings are set according to security slider mode',
        slider_mode     => 4,
        pre             => \&set_slider_mode,
        post            => \&reset_slider_mode,
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
284
    },
boklm's avatar
boklm committed
285
    {
boklm's avatar
boklm committed
286
        name            => 'dom-objects-enumeration',
287
        type            => 'marionette',
boklm's avatar
boklm committed
288
        descr           => 'Check the list of DOM Objects exposed in the global namespace',
boklm's avatar
boklm committed
289
    },
290 291 292 293 294
    {
        name            => 'dom-objects-enumeration-worker',
        type            => 'marionette',
        descr           => 'Check the list of DOM Objects exposed in a Worker context',
    },
295
    {
boklm's avatar
boklm committed
296
        name            => 'navigation-timing',
297
        type            => 'marionette',
boklm's avatar
boklm committed
298 299
        descr           => 'Check that the Navigation Timing API is really disabled',
        use_net         => 1,
300
    },
301
    {
boklm's avatar
boklm committed
302
        name            => 'resource-timing',
303
        type            => 'marionette',
304
        descr           => 'Check that the Resource Timing API is really disabled',
boklm's avatar
boklm committed
305
        use_net         => 1,
306 307
        # To check that the test fails when resource timing is enabled,
        # uncomment this:
308 309
        #prefs           => {
        #    'dom.enable_resource_timing' => 'true',
310 311 312 313 314 315 316 317 318 319
        #},
    },
    {
        name            => 'user-timing',
        type            => 'marionette',
        descr           => 'Check that the User Timing API is really disabled',
        use_net         => 1,
        # To check that the test fails when user timing is enabled,
        # uncomment this:
        #prefs           => {
320
        #    'dom.enable_user_timing' => 'true',
321
        #},
322
    },
323 324 325 326 327 328 329 330 331 332 333 334 335 336
    {
        name            => 'user-timing-worker',
        type            => 'marionette',
        marionette_test => 'page',
        remote          => 0,
        timeout         => 500,
        descr           => 'Check that the User Timing API in Worker context is really disabled',
        use_net         => 1,
        # To check that the test fails when user timing is enabled,
        # uncomment this:
        #prefs           => {
        #    'dom.enable_user_timing' => 'true',
        #},
    },
337 338 339 340 341 342 343 344 345 346 347
    {
        name            => 'performance-observer',
        type            => 'marionette',
        descr           => 'Check that the Performance Observer API is really disabled',
        use_net         => 1,
        # To check that the test fails when performance observer is enabled,
        # uncomment this:
        #prefs           => {
        #    'dom.enable_performance_observer' => 'true',
        #},
    },
348
    {
boklm's avatar
boklm committed
349
        name            => 'searchengines',
350
        type            => 'marionette',
boklm's avatar
boklm committed
351
        descr           => 'Check that we have the default search engines set',
352
    },
boklm's avatar
boklm committed
353
    {
boklm's avatar
boklm committed
354
        name            => 'noscript',
355
        type            => 'marionette',
boklm's avatar
boklm committed
356 357 358
        descr           => 'Check that noscript options are working',
        use_net         => 1,
        prefs           => {
359
            'extensions.torbutton.security_slider' => 2,
boklm's avatar
boklm committed
360
        },
boklm's avatar
boklm committed
361
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
362
    },
boklm's avatar
boklm committed
363
    {
boklm's avatar
boklm committed
364
        name            => 'fp_screen_dimensions',
365
        type            => 'marionette',
boklm's avatar
boklm committed
366
        descr           => 'Check that screen dimensions are spoofed correctly',
367 368
    },
    {
boklm's avatar
boklm committed
369
        name            => 'fp_screen_coords',
370
        type            => 'marionette',
boklm's avatar
boklm committed
371
        descr           => 'Check that screenX, screenY, screenLeft, screenTop, mozInnerScreenX, mozInnerScreenY are 0',
372 373
    },
    {
boklm's avatar
boklm committed
374
        name            => 'fp_plugins',
375
        type            => 'marionette',
boklm's avatar
boklm committed
376
        descr           => 'Check that plugins are disabled',
377 378
    },
    {
boklm's avatar
boklm committed
379
        name            => 'fp_useragent',
380
        type            => 'marionette',
boklm's avatar
boklm committed
381
        descr           => 'Check that userAgent is as expected',
382 383
    },
    {
boklm's avatar
boklm committed
384
        name            => 'fp_navigator',
385
        type            => 'marionette',
boklm's avatar
boklm committed
386 387 388 389
        descr           => 'Check that navigator properties are as expected',
    },
    {
        name            => 'play_videos',
390
        type            => 'marionette',
boklm's avatar
boklm committed
391 392
        descr           => 'Play some videos',
        use_net         => 1,
393
        marionette_test => 'page',
boklm's avatar
boklm committed
394 395 396 397 398
        remote          => 1,
        timeout         => 50000,
    },
    {
        name            => 'svg-disable',
boklm's avatar
boklm committed
399
        type            => 'marionette',
boklm's avatar
boklm committed
400
        descr           => 'Check if disabling svg is working',
boklm's avatar
boklm committed
401
        marionette_test => 'svg',
boklm's avatar
boklm committed
402 403
        use_net         => 1,
        prefs           => {
404 405 406
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'false',
        },
boklm's avatar
boklm committed
407
        enable          => sub { $OSNAME eq 'linux' },
408 409
    },
    {
boklm's avatar
boklm committed
410
        name            => 'svg-enable',
boklm's avatar
boklm committed
411
        type            => 'marionette',
boklm's avatar
boklm committed
412
        descr           => 'Check if enabling svg is working',
boklm's avatar
boklm committed
413
        marionette_test => 'svg',
boklm's avatar
boklm committed
414 415
        use_net         => 1,
        prefs           => {
416 417 418
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'true',
        },
boklm's avatar
boklm committed
419
        enable          => sub { $OSNAME eq 'linux' },
420
    },
421 422 423 424 425 426
    {
        name            => 'download_pdf',
        type            => 'marionette',
        descr           => 'Check if download of PDF is working (#19402)',
        use_net         => 1,
    },
boklm's avatar
boklm committed
427 428 429 430 431 432 433
    {
        name            => 'pinning_now',
        type            => 'marionette',
        descr           => 'Check if static public key pinning is working (#20149)',
        marionette_test => 'pinning',
        use_net         => 1,
    },
434 435 436 437 438 439
);

sub toggle_https_everywhere {
    my ($tbbinfos, $t) = @_;
    my $prefs = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere@eff.org/defaults/preferences/preferences.js';
440 441 442
    my $prefs_eff = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere-eff@eff.org/defaults/preferences/preferences.js';
    $prefs = $prefs_eff unless -f $prefs;
443 444 445 446 447 448 449 450 451 452 453 454 455
    my @f = read_file($prefs);
    foreach (@f) {
        if ($t) {
            s/pref\("extensions\.https_everywhere\.globalEnabled",false\);
             /pref("extensions.https_everywhere.globalEnabled",true);/x;
        } else {
            s/pref\("extensions\.https_everywhere\.globalEnabled",true\);
             /pref("extensions.https_everywhere.globalEnabled",false);/x;
        }
    }
    write_file($prefs, @f);
}

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
sub set_test_prefs {
    my ($tbbinfos, $t) = @_;
    return unless $t->{prefs};
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    copy $prefs, "$prefs.backup";
    my $new_prefs = '';
    foreach my $prefname (sort keys %{$t->{prefs}}) {
        $new_prefs .= "pref(\"$prefname\", $t->{prefs}{$prefname});\n";
    }
    write_file($prefs, {append => 1}, $new_prefs);
}

sub reset_test_prefs {
    my ($tbbinfos, $t) = @_;
    return unless $t->{prefs};
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    move "$prefs.backup", $prefs;
}

boklm's avatar
boklm committed
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
sub set_slider_mode {
    my ($tbbinfos, $t) = @_;
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    copy $prefs, "$prefs.slider_backup";
    write_file($prefs, {append => 1},
      'pref("extensions.torbutton.security_custom", false);' . "\n" .
      "pref(\"extensions.torbutton.security_slider\", $t->{slider_mode});\n");
}

sub reset_slider_mode {
    my ($tbbinfos, $t) = @_;
    my $prefs = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    move "$prefs.slider_backup", $prefs;
}

490 491 492
sub tbb_binfiles {
    my ($tbbinfos, $test) = @_;
    return $tbbinfos->{binfiles} if $tbbinfos->{binfiles};
493
    my %binfiles;
494 495 496 497
    my %wanted_types = (
        'application/x-executable-file' => 1,
        'application/x-ms-dos-executable' => 1,
    );
498 499 500
    my $wanted = sub {
        return unless -f $File::Find::name;
        my $type = File::Type->new->checktype_filename($File::Find::name);
501
        return unless $wanted_types{$type};
boklm's avatar
boklm committed
502 503 504
        my $name = $File::Find::name;
        $name =~ s/^$tbbinfos->{tbbdir}\///;
        $binfiles{$name} = 1;
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
    };
    find($wanted, $tbbinfos->{tbbdir});
    return $tbbinfos->{binfiles} = [ keys %binfiles ];
}

sub list_tests {
    foreach my $test (@tests) {
        print "$test->{name} ($test->{type})\n   $test->{descr}\n\n";
    }
}

sub get_tbbfile {
    my ($tbbinfos) = @_;
    $tbbinfos->{tbbfile_orig} = $tbbinfos->{tbbfile};
    if ($tbbinfos->{tbbfile} =~ m/^https?:\/\//) {
        my (undef, undef, $file) = File::Spec->splitpath($tbbinfos->{tbbfile});
        my $output = $options->{'download-dir'} ?
                "$options->{'download-dir'}/$file" : "$tbbinfos->{tmpdir}/$file";
        return $output if -f $output;
        print "Downloading $tbbinfos->{tbbfile}\n";
        my $ua = LWP::UserAgent->new;
        my $resp = $ua->get($tbbinfos->{tbbfile}, ':content_file' => $output);
        exit_error "Error downloading $tbbinfos->{tbbfile}:\n" . $resp->status_line
                unless $resp->is_success;
        $tbbinfos->{tbbfile} = $output;
    }
    exit_error "File $tbbinfos->{tbbfile} does not exist"
                unless -f $tbbinfos->{tbbfile};
}

sub extract_tbb {
    my ($tbbinfos) = @_;
    exit_error "Can't open file $tbbinfos->{tbbfile}" unless -f $tbbinfos->{tbbfile};
    my $tbbfile = File::Spec->rel2abs($tbbinfos->{tbbfile});
    my $tmpdir = $tbbinfos->{tmpdir};
    chdir $tmpdir;
    if ($tbbinfos->{os} eq 'Linux') {
        system('tar', 'xf', $tbbfile);
543 544 545 546 547
        if ($tbbinfos->{language} eq 'ALL') {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser";
        } else {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser_$tbbinfos->{language}";
        }
548
        $tbbinfos->{tbbdir} .= '/Browser';
549 550 551 552
    } elsif ($tbbinfos->{os} eq 'Windows') {
        my (undef, undef, $f) = File::Spec->splitpath($tbbfile);
        copy($tbbfile, "$tmpdir/$f");
        system('7z', 'x', $f);
553
        $tbbinfos->{tbbdir} = "$tmpdir/torbrowser/Browser";
554 555 556 557 558
        move("$tmpdir/\$_OUTDIR", "$tmpdir/torbrowser") if -d "$tmpdir/\$_OUTDIR";
        if (-d "$tmpdir/Browser") {
            mkdir "$tmpdir/torbrowser";
            move("$tmpdir/Browser", "$tmpdir/torbrowser/Browser");
        }
559
        move ("$tmpdir/Start Tor Browser.exe", "$tmpdir/torbrowser/");
boklm's avatar
boklm committed
560 561 562 563 564 565
    } elsif ($tbbinfos->{os} eq 'MacOSX') {
        my $mountpoint = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
        system('hdiutil', 'mount', '-mountpoint', $mountpoint, $tbbfile);
        system('cp', '-a', "$mountpoint/TorBrowser.app", "$tmpdir/TorBrowser.app");
        system('hdiutil', 'unmount', $mountpoint);
        $tbbinfos->{tbbdir} = "$tmpdir/TorBrowser.app";
566 567 568 569 570 571 572 573 574 575 576 577 578
    }
}

sub xvfb_run {
    my ($test) = @_;
    return () unless $options->{xvfb};
    my $resolution = $test->{resolution} ? $test->{resolution}
                                         : $options->{resolution};
    return ('xvfb-run', '--auto-servernum', '-s', "-screen 0 ${resolution}x24");
}

sub check_opened_connections {
    my ($tbbinfos, $test) = @_;
579 580 581 582 583 584
    my %bad_connections =  %{$test->{results}{connections}};
    delete $bad_connections{"127.0.0.1:$options->{'tor-control-port'}"};
    delete $bad_connections{"127.0.0.1:$options->{'tor-socks-port'}"};
    # For some reasons, tor-browser creates two connections to the default
    # socks port even when when TOR_SOCKS_PORT is set
    # https://lists.torproject.org/pipermail/tbb-dev/2014-May/000050.html
boklm's avatar
boklm committed
585 586 587 588
    if (defined $bad_connections{'127.0.0.1:9150'}
        && $bad_connections{'127.0.0.1:9150'} <= 2) {
        delete $bad_connections{'127.0.0.1:9150'}
    }
589 590 591 592
    if (%bad_connections) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
593
    $test->{clean_strace} //= !%bad_connections;
594
    $test->{results}{bad_connections} = \%bad_connections;
595 596 597 598
}

sub check_modified_files {
    my ($tbbinfos, $test) = @_;
599
    my @bad_modified_files = @{$test->{results}{modified_files}};
600 601 602 603
    if (@bad_modified_files) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
604
    $test->{clean_strace} //= !@bad_modified_files;
605 606 607
    $test->{results}{bad_modified_files} = \@bad_modified_files;
}

608 609
sub clean_strace {
    my ($tbbinfos, $test) = @_;
610
    return unless $test->{clean_strace};
611 612
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    unlink $logfile;
boklm's avatar
boklm committed
613
    unlink "$logfile.tmp";
614 615
}

616 617 618
sub parse_strace {
    my ($tbbinfos, $test) = @_;
    my %ignore_files = map { $_ => 1 } qw(/dev/null /dev/tty);
619 620
    my @ignore_re = ( qr/^\/dev\/dri/ );
    push @ignore_re, qr/^$test->{workspace}/ if $test->{workspace};
621 622 623 624 625
    my %files;
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    $test->{results}{connections} = {};
    my %modified_files;
    my %removed_files;
626 627 628 629 630
    if (-f "$logfile.tmp") {
        my $txt = read_file("$logfile.tmp");
        write_file($logfile, { append => 1 }, $txt);
        unlink "$logfile.tmp";
    }
631
    my @lines = read_file($logfile) if -f $logfile;
632
    LINE: foreach my $line (@lines) {
633 634 635 636 637
        if ($line =~ m/^\d+ open\("((?:[^"\\]++|\\.)*+)", ([^\)]+)/ ||
            $line =~ m/^\d+ openat\([^,]+, "((?:[^"\\]++|\\.)*+)", ([^\)]+)/) {
            next if $2 =~ m/O_RDONLY/;
            next if $1 =~ m/^$tbbinfos->{tbbdir}/;
            next if $ignore_files{$1};
638 639 640
            if ($ENV{'MOZMILL_SCREENSHOTS'}) {
                next if $1 =~ m/^$ENV{'MOZMILL_SCREENSHOTS'}/;
            }
641 642 643
            foreach my $re (@ignore_re) {
                next LINE if $1 =~ m/$re/;
            }
644 645 646 647
            $modified_files{$1}++;
        }
        if ($line =~ m/^\d+ unlink\("((?:[^"\\]++|\\.)*+)"/) {
            next if $1 =~ m/^$tbbinfos->{tbbdir}/;
648 649 650 651
            next if $ignore_files{$1};
            foreach my $re (@ignore_re) {
                next LINE if $1 =~ m/$re/;
            }
652 653 654
            $removed_files{$1}++;
            delete $modified_files{$1} unless -f $1;
        }
boklm's avatar
boklm committed
655
        if ($line =~ m/^\d+ connect\(\d+, \{sa_family=AF_INET, sin_port=htons\((\d+)\), sin_addr=inet_addr\("((?:[^"\\]++|\\.)*+)"\)/) {
656
            $test->{results}{connections}{"$2:$1"}++;
657 658
        }
    }
659 660
    $test->{results}{modified_files} = [ keys %modified_files ];
    $test->{results}{removed_files} = [ keys %removed_files ];
661 662 663 664 665 666 667 668 669
}

sub ff_wrapper {
    my ($tbbinfos, $test) = @_;
    my $wrapper_file = "$tbbinfos->{tbbdir}/ff_wrapper";
    return $wrapper_file if -f $wrapper_file;
    my $wrapper = <<EOF;
#!/bin/sh
set -e
670
export HOME="$tbbinfos->{tbbdir}"
671
export LD_LIBRARY_PATH="$tbbinfos->{tbbdir}:$tbbinfos->{tordir}"
672 673
export FONTCONFIG_PATH="\${HOME}/TorBrowser/Data/fontconfig"
export FONTCONFIG_FILE="fonts.conf"
674 675 676 677 678 679 680
exec \'$tbbinfos->{ffbin}\' "\$@"
EOF
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

681
sub ff_strace_wrapper {
682 683
    my ($tbbinfos, $test) = @_;
    my $ff_wrapper = ff_wrapper($tbbinfos, $test);
684
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
685 686
    my $wrapper = <<EOF;
#!/bin/sh
687 688 689 690 691 692
if [ -f $logfile.tmp ]
then
   cat $logfile.tmp >> $logfile
   rm $logfile.tmp
fi
echo \$@ >> /tmp/ff_run.log
693 694 695 696 697
strace -f -o $logfile.tmp -- \'$ff_wrapper\' "\$@"
exit_code=\$?
cat $logfile.tmp >> $logfile
rm $logfile.tmp
exit \$?
698 699 700 701 702 703 704 705 706 707 708 709
EOF
    my $wrapper_file = "$tbbinfos->{tbbdir}/ff_$test->{name}";
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

sub ffbin_path {
    my ($tbbinfos, $test) = @_;
    if ($OSNAME eq 'cygwin') {
        return winpath("$tbbinfos->{ffbin}.exe");
    }
710
    my %t = map { $_ => 1 } qw(marionette);
711
    if ($options->{use_strace} && $t{$test->{type}}) {
boklm's avatar
boklm committed
712 713
        return ff_strace_wrapper($tbbinfos, $test);
    }
boklm's avatar
boklm committed
714
    return $tbbinfos->{ffbin} if $OSNAME eq 'darwin';
715
    return ff_wrapper($tbbinfos, $test);
716 717
}

718 719 720 721 722 723 724 725 726 727 728 729
sub marionette_export_options {
    my ($tbbinfos, $test) = @_;
    my $options_file = File::Temp->new();
    my $json = {
        options  => clone_strip_coderef($options),
        test     => clone_strip_coderef($test),
        tbbinfos => clone_strip_coderef({ %$tbbinfos, tests => undef }),
    };
    write_file($options_file, encode_json($json));
    return $options_file;
}

730 731 732 733 734 735 736
sub marionette_run {
    my ($tbbinfos, $test) = @_;
    if ($test->{tried} && $test->{use_net}) {
        TBBTestSuite::Tests::TorBootstrap::send_newnym($tbbinfos);
    }
    set_test_prefs($tbbinfos, $test);

737 738
    my $options_file = marionette_export_options($tbbinfos, $test);
    $ENV{TESTSUITE_DATA_FILE} = winpath($options_file);
739 740
    my $result_file_html = "$tbbinfos->{'results-dir'}/$test->{name}.html";
    my $result_file_txt = "$tbbinfos->{'results-dir'}/$test->{name}.txt";
741 742
    $test->{workspace} = "$tbbinfos->{'results-dir'}/$test->{name}_ws";
    mkdir $test->{workspace};
743 744
    #--log-unittest  ./res.txt --log-html ./res.html
    my $bin = $OSNAME eq 'cygwin' ? 'Scripts' : 'bin';
745
    my $marionette_test = $test->{marionette_test} // $test->{name};
746
    my $pypath = $ENV{PYTHONPATH};
boklm's avatar
boklm committed
747 748 749 750
    my $old_pypath = $ENV{PYTHONPATH};
    $ENV{PYTHONPATH} = winpath("$FindBin::Bin/marionette/tor_browser_tests/lib");
    my $sep = $OSNAME eq 'cygwin' ? ';' : ':';
    $ENV{PYTHONPATH} .= $sep . $old_pypath if $old_pypath;
751 752 753
    $test->{screenshots} = [];
    my $screenshots_tmp = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
    $ENV{'MARIONETTE_SCREENSHOTS'} = winpath($screenshots_tmp);
754 755 756 757 758
    system(xvfb_run($test), "$FindBin::Bin/virtualenv-marionette/$bin/tor-browser-tests",
        '--log-unittest', winpath($result_file_txt),
        '--log-html', winpath($result_file_html),
        '--binary', ffbin_path($tbbinfos, $test),
        '--profile', winpath($tbbinfos->{ffprofiledir}),
759
        $OSNAME eq 'cygwin' ? () : ('--workspace', $test->{workspace}),
760
        winpath("$FindBin::Bin/marionette/tor_browser_tests/test_${marionette_test}.py"));
761
    $ENV{PYTHONPATH} = $pypath;
762
    my @txt_log = -f $result_file_txt ? read_file($result_file_txt) : ('NoFile');
763 764 765
    my $res_line = shift @txt_log;
    $test->{results}{success} = $res_line eq ".\n" || $res_line eq ".\r\n";
    $test->{results}{log} = join '', @txt_log;
766 767 768
    my $i = 0;
    for my $screenshot_file (sort glob "$screenshots_tmp/*.png") {
        move($screenshot_file, "$tbbinfos->{'results-dir'}/$test->{name}-$i.png");
769
        screenshot_thumbnail($tbbinfos->{'results-dir'}, "$test->{name}-$i.png");
770 771 772
        push @{$test->{screenshots}}, "$test->{name}-$i.png";
        $i++;
    }
773 774 775 776
    reset_test_prefs($tbbinfos, $test);
    parse_strace($tbbinfos, $test);
    check_opened_connections($tbbinfos, $test);
    check_modified_files($tbbinfos, $test);
777
    clean_strace($tbbinfos, $test);
778 779
}

780 781
sub set_tbbpaths {
    my ($tbbinfos) = @_;
782 783 784
    $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/firefox";
    $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/TorBrowser/Tor";
    $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/TorBrowser/Data";
boklm's avatar
boklm committed
785 786
    if ($tbbinfos->{os} eq 'MacOSX') {
        $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/Contents/MacOS/firefox";
787 788 789 790 791 792 793 794 795
        unless ($tbbinfos->{version} =~ m/^5./) {
            $tbbinfos->{ffprofiledir} = "$tbbinfos->{tbbdir}/Contents/Resources/distribution";
            $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/Contents/Resources/TorBrowser/Tor";
            $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/../TorBrowser-data";
            $tbbinfos->{torrcdefaults} = "$tbbinfos->{tordir}/torrc-defaults";
            $tbbinfos->{torgeoip} = "$tbbinfos->{tordir}/geoip";
            mkdir $tbbinfos->{datadir} unless -d $tbbinfos->{datadir};
            mkdir "$tbbinfos->{datadir}/Tor" unless -d "$tbbinfos->{datadir}/Tor";
        }
boklm's avatar
boklm committed
796
    }
797 798
    $tbbinfos->{torrcdefaults} //= "$tbbinfos->{datadir}/Tor/torrc-defaults";
    $tbbinfos->{torgeoip} //= "$tbbinfos->{datadir}/Tor/geoip";
799
    $tbbinfos->{torbin} = "$tbbinfos->{tordir}/tor";
boklm's avatar
boklm committed
800
    $tbbinfos->{ptdir} = winpath("$tbbinfos->{tordir}/PluggableTransports");
801
    $tbbinfos->{ffprofiledir} //= "$tbbinfos->{datadir}/Browser/profile.default";
802 803
}

boklm's avatar
boklm committed
804 805 806 807
sub new {
    my ($ts, $testsuite) = @_;
    $testsuite->{type} = 'browserbundle';
    $testsuite->{tests} = [ map { { %$_ } } @tests ];
808 809
    return undef unless $testsuite->{os} eq $options->{os};
    return undef unless $testsuite->{arch} eq $options->{arch};
boklm's avatar
boklm committed
810 811 812
    return bless $testsuite, $ts;
}

813 814 815 816 817 818 819 820 821 822
sub pre_tests {
    my ($tbbinfos) = @_;
    get_tbbfile($tbbinfos);
    if ($tbbinfos->{sha256sum} &&
        $tbbinfos->{sha256sum} ne sha256_hex(read_file($tbbinfos->{tbbfile}))) {
        exit_error "Wrong sha256sum for $tbbinfos->{tbbfile}";
    }
    $tbbinfos->{sha256sum} //= sha256_hex(read_file($tbbinfos->{tbbfile}));
    extract_tbb($tbbinfos);
    set_tbbpaths($tbbinfos);
823 824 825 826
    my $prefs_file = "$tbbinfos->{ffprofiledir}/preferences/extension-overrides.js";
    open(my $prefs_fh, '>>', $prefs_file);
    print $prefs_fh 'pref("extensions.torbutton.prompted_language", true);', "\n";
    close $prefs_fh;
827
    chdir $tbbinfos->{tbbdir} || exit_error "Can't enter directory $tbbinfos->{tbbdir}";
boklm's avatar
boklm committed
828
    copy "$FindBin::Bin/data/cert_override.txt",
boklm's avatar
boklm committed
829
          "$tbbinfos->{ffprofiledir}/cert_override.txt";
830
    $ENV{TOR_SKIP_LAUNCH} = 1;
831 832
    $ENV{TOR_SOCKS_PORT} = $options->{'tor-socks-port'};
    $ENV{TOR_CONTROL_PORT} = $options->{'tor-control-port'};
boklm's avatar
boklm committed
833 834 835
    if ($options->{xdummy}) {
        $tbbinfos->{Xdisplay} = start_X("$tbbinfos->{'results-dir'}/xorg.log");
    }
836 837 838 839 840
}

sub post_tests {
    my ($tbbinfos) = @_;
    TBBTestSuite::Tests::TorBootstrap::stop_tor($tbbinfos);
boklm's avatar
boklm committed
841
    stop_X($tbbinfos->{Xdisplay}) if $options->{xdummy};
842 843 844
}

1;