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 31.2 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);
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 26 27 28 29 30 31

my $screenshot_thumbnail;
BEGIN {
    # For some reason that I did not understand yet, Image::Magick does
    # not work on Windows, so we're not creating thumbnails if we're
    # on Windows. In that case, the thumbnails should be created by the
    # server that receives the results.
boklm's avatar
boklm committed
32
    if ($OSNAME ne 'cygwin' && $OSNAME ne 'darwin') {
33 34 35 36 37 38 39
        require TBBTestSuite::Thumbnail;
        $screenshot_thumbnail = \&TBBTestSuite::Thumbnail::screenshot_thumbnail;
    } else {
        $screenshot_thumbnail = sub { };
    }
}

boklm's avatar
boklm committed
40 41 42
sub test_types {
    return {
        tor_bootstrap => \&TBBTestSuite::Tests::TorBootstrap::start_tor,
43
        marionette    => \&marionette_run,
boklm's avatar
boklm committed
44 45 46 47
        virustotal    => \&virustotal_run,
        command       => \&command_run,
    };
}
48

boklm's avatar
boklm committed
49 50 51
sub type {
    'browserbundle';
}
52

boklm's avatar
boklm committed
53 54 55
sub description {
    'Tor Browser Bundle integration tests';
}
56

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

sub toggle_https_everywhere {
    my ($tbbinfos, $t) = @_;
    my $prefs = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere@eff.org/defaults/preferences/preferences.js';
454 455 456
    my $prefs_eff = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere-eff@eff.org/defaults/preferences/preferences.js';
    $prefs = $prefs_eff unless -f $prefs;
457 458 459 460 461 462 463 464 465 466 467 468 469
    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);
}

470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
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
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
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;
}

504 505 506
sub tbb_binfiles {
    my ($tbbinfos, $test) = @_;
    return $tbbinfos->{binfiles} if $tbbinfos->{binfiles};
507
    my %binfiles;
508 509 510 511
    my %wanted_types = (
        'application/x-executable-file' => 1,
        'application/x-ms-dos-executable' => 1,
    );
512 513 514
    my $wanted = sub {
        return unless -f $File::Find::name;
        my $type = File::Type->new->checktype_filename($File::Find::name);
515
        return unless $wanted_types{$type};
boklm's avatar
boklm committed
516 517 518
        my $name = $File::Find::name;
        $name =~ s/^$tbbinfos->{tbbdir}\///;
        $binfiles{$name} = 1;
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
    };
    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);
557 558 559 560 561
        if ($tbbinfos->{language} eq 'ALL') {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser";
        } else {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser_$tbbinfos->{language}";
        }
562
        $tbbinfos->{tbbdir} .= '/Browser';
563 564 565 566
    } elsif ($tbbinfos->{os} eq 'Windows') {
        my (undef, undef, $f) = File::Spec->splitpath($tbbfile);
        copy($tbbfile, "$tmpdir/$f");
        system('7z', 'x', $f);
567
        $tbbinfos->{tbbdir} = "$tmpdir/torbrowser/Browser";
568 569 570 571 572
        move("$tmpdir/\$_OUTDIR", "$tmpdir/torbrowser") if -d "$tmpdir/\$_OUTDIR";
        if (-d "$tmpdir/Browser") {
            mkdir "$tmpdir/torbrowser";
            move("$tmpdir/Browser", "$tmpdir/torbrowser/Browser");
        }
573
        move ("$tmpdir/Start Tor Browser.exe", "$tmpdir/torbrowser/");
boklm's avatar
boklm committed
574 575 576 577 578 579
    } 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";
580 581 582 583 584 585 586 587 588 589 590 591 592
    }
}

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) = @_;
593 594 595 596 597 598
    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
599 600 601 602
    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'}
    }
603 604 605 606
    if (%bad_connections) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
607
    $test->{clean_strace} //= !%bad_connections;
608
    $test->{results}{bad_connections} = \%bad_connections;
609 610 611 612
}

sub check_modified_files {
    my ($tbbinfos, $test) = @_;
613
    my @bad_modified_files = @{$test->{results}{modified_files}};
614 615 616 617
    if (@bad_modified_files) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
618
    $test->{clean_strace} //= !@bad_modified_files;
619 620 621
    $test->{results}{bad_modified_files} = \@bad_modified_files;
}

622 623
sub clean_strace {
    my ($tbbinfos, $test) = @_;
624
    return unless $test->{clean_strace};
625 626
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    unlink $logfile;
boklm's avatar
boklm committed
627
    unlink "$logfile.tmp";
628 629
}

630 631 632
sub parse_strace {
    my ($tbbinfos, $test) = @_;
    my %ignore_files = map { $_ => 1 } qw(/dev/null /dev/tty);
633 634
    my @ignore_re = ( qr/^\/dev\/dri/ );
    push @ignore_re, qr/^$test->{workspace}/ if $test->{workspace};
635 636 637 638 639
    my %files;
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    $test->{results}{connections} = {};
    my %modified_files;
    my %removed_files;
640 641 642 643 644
    if (-f "$logfile.tmp") {
        my $txt = read_file("$logfile.tmp");
        write_file($logfile, { append => 1 }, $txt);
        unlink "$logfile.tmp";
    }
645
    my @lines = read_file($logfile) if -f $logfile;
646
    LINE: foreach my $line (@lines) {
647 648 649 650 651
        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};
652 653 654
            if ($ENV{'MOZMILL_SCREENSHOTS'}) {
                next if $1 =~ m/^$ENV{'MOZMILL_SCREENSHOTS'}/;
            }
655 656 657
            foreach my $re (@ignore_re) {
                next LINE if $1 =~ m/$re/;
            }
658 659 660 661
            $modified_files{$1}++;
        }
        if ($line =~ m/^\d+ unlink\("((?:[^"\\]++|\\.)*+)"/) {
            next if $1 =~ m/^$tbbinfos->{tbbdir}/;
662 663 664 665
            next if $ignore_files{$1};
            foreach my $re (@ignore_re) {
                next LINE if $1 =~ m/$re/;
            }
666 667 668 669 670
            $removed_files{$1}++;
            delete $modified_files{$1} unless -f $1;
        }
        if ($line =~ m/^\d+ connect\(\d+, {sa_family=AF_INET, sin_port=htons\((\d+)\), sin_addr=inet_addr\("((?:[^"\\]++|\\.)*+)"\)/) {
            $test->{results}{connections}{"$2:$1"}++;
671 672
        }
    }
673 674
    $test->{results}{modified_files} = [ keys %modified_files ];
    $test->{results}{removed_files} = [ keys %removed_files ];
675 676 677 678 679 680 681 682 683
}

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
684
export HOME="$tbbinfos->{tbbdir}"
685
export LD_LIBRARY_PATH="$tbbinfos->{tbbdir}:$tbbinfos->{tordir}"
686 687
export FONTCONFIG_PATH="\${HOME}/TorBrowser/Data/fontconfig"
export FONTCONFIG_FILE="fonts.conf"
688 689 690 691 692 693 694
exec \'$tbbinfos->{ffbin}\' "\$@"
EOF
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

695
sub ff_strace_wrapper {
696 697
    my ($tbbinfos, $test) = @_;
    my $ff_wrapper = ff_wrapper($tbbinfos, $test);
698
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
699 700
    my $wrapper = <<EOF;
#!/bin/sh
701 702 703 704 705 706
if [ -f $logfile.tmp ]
then
   cat $logfile.tmp >> $logfile
   rm $logfile.tmp
fi
echo \$@ >> /tmp/ff_run.log
707 708 709 710 711
strace -f -o $logfile.tmp -- \'$ff_wrapper\' "\$@"
exit_code=\$?
cat $logfile.tmp >> $logfile
rm $logfile.tmp
exit \$?
712 713 714 715 716 717 718 719 720 721 722 723
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");
    }
724
    my %t = map { $_ => 1 } qw(marionette);
725
    if ($options->{use_strace} && $t{$test->{type}}) {
boklm's avatar
boklm committed
726 727
        return ff_strace_wrapper($tbbinfos, $test);
    }
boklm's avatar
boklm committed
728
    return $tbbinfos->{ffbin} if $OSNAME eq 'darwin';
729
    return ff_wrapper($tbbinfos, $test);
730 731
}

732 733 734 735 736 737 738 739 740 741 742 743
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;
}

744 745 746 747 748 749 750
sub marionette_run {
    my ($tbbinfos, $test) = @_;
    if ($test->{tried} && $test->{use_net}) {
        TBBTestSuite::Tests::TorBootstrap::send_newnym($tbbinfos);
    }
    set_test_prefs($tbbinfos, $test);

751 752
    my $options_file = marionette_export_options($tbbinfos, $test);
    $ENV{TESTSUITE_DATA_FILE} = winpath($options_file);
753 754
    my $result_file_html = "$tbbinfos->{'results-dir'}/$test->{name}.html";
    my $result_file_txt = "$tbbinfos->{'results-dir'}/$test->{name}.txt";
755 756
    $test->{workspace} = "$tbbinfos->{'results-dir'}/$test->{name}_ws";
    mkdir $test->{workspace};
757 758
    #--log-unittest  ./res.txt --log-html ./res.html
    my $bin = $OSNAME eq 'cygwin' ? 'Scripts' : 'bin';
759
    my $marionette_test = $test->{marionette_test} // $test->{name};
760
    my $pypath = $ENV{PYTHONPATH};
boklm's avatar
boklm committed
761 762 763 764
    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;
765 766 767
    $test->{screenshots} = [];
    my $screenshots_tmp = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
    $ENV{'MARIONETTE_SCREENSHOTS'} = winpath($screenshots_tmp);
768 769 770 771 772
    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}),
773
        $OSNAME eq 'cygwin' ? () : ('--workspace', $test->{workspace}),
774
        winpath("$FindBin::Bin/marionette/tor_browser_tests/test_${marionette_test}.py"));
775
    $ENV{PYTHONPATH} = $pypath;
776 777 778 779
    my @txt_log = read_file($result_file_txt);
    my $res_line = shift @txt_log;
    $test->{results}{success} = $res_line eq ".\n" || $res_line eq ".\r\n";
    $test->{results}{log} = join '', @txt_log;
780 781 782 783 784 785 786
    my $i = 0;
    for my $screenshot_file (sort glob "$screenshots_tmp/*.png") {
        move($screenshot_file, "$tbbinfos->{'results-dir'}/$test->{name}-$i.png");
        $screenshot_thumbnail->($tbbinfos->{'results-dir'}, "$test->{name}-$i.png");
        push @{$test->{screenshots}}, "$test->{name}-$i.png";
        $i++;
    }
787 788 789 790
    reset_test_prefs($tbbinfos, $test);
    parse_strace($tbbinfos, $test);
    check_opened_connections($tbbinfos, $test);
    check_modified_files($tbbinfos, $test);
791
    clean_strace($tbbinfos, $test);
792 793
}

794 795
sub set_tbbpaths {
    my ($tbbinfos) = @_;
796 797 798
    $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/firefox";
    $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/TorBrowser/Tor";
    $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/TorBrowser/Data";
boklm's avatar
boklm committed
799 800
    if ($tbbinfos->{os} eq 'MacOSX') {
        $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/Contents/MacOS/firefox";
801 802 803 804 805 806 807 808 809
        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
810
    }
811 812
    $tbbinfos->{torrcdefaults} //= "$tbbinfos->{datadir}/Tor/torrc-defaults";
    $tbbinfos->{torgeoip} //= "$tbbinfos->{datadir}/Tor/geoip";
813
    $tbbinfos->{torbin} = "$tbbinfos->{tordir}/tor";
boklm's avatar
boklm committed
814
    $tbbinfos->{ptdir} = winpath("$tbbinfos->{tordir}/PluggableTransports");
815
    $tbbinfos->{ffprofiledir} //= "$tbbinfos->{datadir}/Browser/profile.default";
816 817
}

boklm's avatar
boklm committed
818 819 820 821
sub new {
    my ($ts, $testsuite) = @_;
    $testsuite->{type} = 'browserbundle';
    $testsuite->{tests} = [ map { { %$_ } } @tests ];
822 823
    return undef unless $testsuite->{os} eq $options->{os};
    return undef unless $testsuite->{arch} eq $options->{arch};
boklm's avatar
boklm committed
824 825 826
    return bless $testsuite, $ts;
}

827 828 829 830 831 832 833 834 835 836
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);
837 838 839 840
    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;
841
    chdir $tbbinfos->{tbbdir} || exit_error "Can't enter directory $tbbinfos->{tbbdir}";
boklm's avatar
boklm committed
842
    copy "$FindBin::Bin/data/cert_override.txt",
boklm's avatar
boklm committed
843
          "$tbbinfos->{ffprofiledir}/cert_override.txt";
844
    $ENV{TOR_SKIP_LAUNCH} = 1;
845 846
    $ENV{TOR_SOCKS_PORT} = $options->{'tor-socks-port'};
    $ENV{TOR_CONTROL_PORT} = $options->{'tor-control-port'};
boklm's avatar
boklm committed
847 848 849
    if ($options->{xdummy}) {
        $tbbinfos->{Xdisplay} = start_X("$tbbinfos->{'results-dir'}/xorg.log");
    }
850 851 852 853 854
}

sub post_tests {
    my ($tbbinfos) = @_;
    TBBTestSuite::Tests::TorBootstrap::stop_tor($tbbinfos);
boklm's avatar
boklm committed
855
    stop_X($tbbinfos->{Xdisplay}) if $options->{xdummy};
856 857 858
}

1;