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);
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
    {
boklm's avatar
boklm committed
312
        name            => 'navigation-timing',
313
        type            => 'marionette',
boklm's avatar
boklm committed
314 315
        descr           => 'Check that the Navigation Timing API is really disabled',
        use_net         => 1,
316
    },
317
    {
boklm's avatar
boklm committed
318
        name            => 'resource-timing',
319
        type            => 'marionette',
320
        descr           => 'Check that the Resource Timing API is really disabled',
boklm's avatar
boklm committed
321
        use_net         => 1,
322 323
        # To check that the test fails when resource timing is enabled,
        # uncomment this:
324 325
        #prefs           => {
        #    'dom.enable_resource_timing' => 'true',
326 327 328 329 330 331 332 333 334 335
        #},
    },
    {
        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           => {
336
        #    'dom.enable_user_timing' => 'true',
337
        #},
338
    },
339 340 341 342 343 344 345 346 347 348 349 350 351 352
    {
        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',
        #},
    },
353 354 355 356 357 358 359 360 361 362 363
    {
        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',
        #},
    },
364
    {
boklm's avatar
boklm committed
365
        name            => 'searchengines',
366
        type            => 'marionette',
boklm's avatar
boklm committed
367
        descr           => 'Check that we have the default search engines set',
368
    },
boklm's avatar
boklm committed
369
    {
boklm's avatar
boklm committed
370
        name            => 'noscript',
371
        type            => 'marionette',
boklm's avatar
boklm committed
372 373 374
        descr           => 'Check that noscript options are working',
        use_net         => 1,
        prefs           => {
375
            'extensions.torbutton.security_slider' => 2,
boklm's avatar
boklm committed
376
        },
boklm's avatar
boklm committed
377
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
378
    },
boklm's avatar
boklm committed
379
    {
boklm's avatar
boklm committed
380
        name            => 'fp_screen_dimensions',
381
        type            => 'marionette',
boklm's avatar
boklm committed
382
        descr           => 'Check that screen dimensions are spoofed correctly',
383 384
    },
    {
boklm's avatar
boklm committed
385
        name            => 'fp_screen_coords',
386
        type            => 'marionette',
boklm's avatar
boklm committed
387
        descr           => 'Check that screenX, screenY, screenLeft, screenTop, mozInnerScreenX, mozInnerScreenY are 0',
388 389
    },
    {
boklm's avatar
boklm committed
390
        name            => 'fp_plugins',
391
        type            => 'marionette',
boklm's avatar
boklm committed
392
        descr           => 'Check that plugins are disabled',
393 394
    },
    {
boklm's avatar
boklm committed
395
        name            => 'fp_useragent',
396
        type            => 'marionette',
boklm's avatar
boklm committed
397
        descr           => 'Check that userAgent is as expected',
398 399
    },
    {
boklm's avatar
boklm committed
400
        name            => 'fp_navigator',
401
        type            => 'marionette',
boklm's avatar
boklm committed
402 403 404 405
        descr           => 'Check that navigator properties are as expected',
    },
    {
        name            => 'play_videos',
406
        type            => 'marionette',
boklm's avatar
boklm committed
407 408
        descr           => 'Play some videos',
        use_net         => 1,
409
        marionette_test => 'page',
boklm's avatar
boklm committed
410 411 412 413 414
        remote          => 1,
        timeout         => 50000,
    },
    {
        name            => 'svg-disable',
boklm's avatar
boklm committed
415
        type            => 'marionette',
boklm's avatar
boklm committed
416
        descr           => 'Check if disabling svg is working',
boklm's avatar
boklm committed
417
        marionette_test => 'svg',
boklm's avatar
boklm committed
418 419
        use_net         => 1,
        prefs           => {
420 421 422
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'false',
        },
boklm's avatar
boklm committed
423
        enable          => sub { $OSNAME eq 'linux' },
424 425
    },
    {
boklm's avatar
boklm committed
426
        name            => 'svg-enable',
boklm's avatar
boklm committed
427
        type            => 'marionette',
boklm's avatar
boklm committed
428
        descr           => 'Check if enabling svg is working',
boklm's avatar
boklm committed
429
        marionette_test => 'svg',
boklm's avatar
boklm committed
430 431
        use_net         => 1,
        prefs           => {
432 433 434
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'true',
        },
boklm's avatar
boklm committed
435
        enable          => sub { $OSNAME eq 'linux' },
436
    },
437 438 439 440 441 442
);

sub toggle_https_everywhere {
    my ($tbbinfos, $t) = @_;
    my $prefs = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere@eff.org/defaults/preferences/preferences.js';
443 444 445
    my $prefs_eff = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere-eff@eff.org/defaults/preferences/preferences.js';
    $prefs = $prefs_eff unless -f $prefs;
446 447 448 449 450 451 452 453 454 455 456 457 458
    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);
}

459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
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
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
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;
}

493 494 495
sub tbb_binfiles {
    my ($tbbinfos, $test) = @_;
    return $tbbinfos->{binfiles} if $tbbinfos->{binfiles};
496
    my %binfiles;
497 498 499 500
    my %wanted_types = (
        'application/x-executable-file' => 1,
        'application/x-ms-dos-executable' => 1,
    );
501 502 503
    my $wanted = sub {
        return unless -f $File::Find::name;
        my $type = File::Type->new->checktype_filename($File::Find::name);
504
        return unless $wanted_types{$type};
boklm's avatar
boklm committed
505 506 507
        my $name = $File::Find::name;
        $name =~ s/^$tbbinfos->{tbbdir}\///;
        $binfiles{$name} = 1;
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 543 544 545
    };
    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);
546 547 548 549 550
        if ($tbbinfos->{language} eq 'ALL') {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser";
        } else {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser_$tbbinfos->{language}";
        }
551
        $tbbinfos->{tbbdir} .= '/Browser';
552 553 554 555
    } elsif ($tbbinfos->{os} eq 'Windows') {
        my (undef, undef, $f) = File::Spec->splitpath($tbbfile);
        copy($tbbfile, "$tmpdir/$f");
        system('7z', 'x', $f);
556
        $tbbinfos->{tbbdir} = "$tmpdir/torbrowser/Browser";
557 558 559 560 561
        move("$tmpdir/\$_OUTDIR", "$tmpdir/torbrowser") if -d "$tmpdir/\$_OUTDIR";
        if (-d "$tmpdir/Browser") {
            mkdir "$tmpdir/torbrowser";
            move("$tmpdir/Browser", "$tmpdir/torbrowser/Browser");
        }
562
        move ("$tmpdir/Start Tor Browser.exe", "$tmpdir/torbrowser/");
boklm's avatar
boklm committed
563 564 565 566 567 568
    } 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";
569 570 571 572 573 574 575 576 577 578 579 580 581
    }
}

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) = @_;
582 583 584 585 586 587
    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
588 589 590 591
    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'}
    }
592 593 594 595
    if (%bad_connections) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
596
    $test->{clean_strace} //= !%bad_connections;
597
    $test->{results}{bad_connections} = \%bad_connections;
598 599 600 601
}

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

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

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

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
673
export HOME="$tbbinfos->{tbbdir}"
674
export LD_LIBRARY_PATH="$tbbinfos->{tbbdir}:$tbbinfos->{tordir}"
675 676
export FONTCONFIG_PATH="\${HOME}/TorBrowser/Data/fontconfig"
export FONTCONFIG_FILE="fonts.conf"
677 678 679 680 681 682 683
exec \'$tbbinfos->{ffbin}\' "\$@"
EOF
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

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

721 722 723 724 725 726 727 728 729 730 731 732
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;
}

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

740 741
    my $options_file = marionette_export_options($tbbinfos, $test);
    $ENV{TESTSUITE_DATA_FILE} = winpath($options_file);
742 743
    my $result_file_html = "$tbbinfos->{'results-dir'}/$test->{name}.html";
    my $result_file_txt = "$tbbinfos->{'results-dir'}/$test->{name}.txt";
744 745
    $test->{workspace} = "$tbbinfos->{'results-dir'}/$test->{name}_ws";
    mkdir $test->{workspace};
746 747
    #--log-unittest  ./res.txt --log-html ./res.html
    my $bin = $OSNAME eq 'cygwin' ? 'Scripts' : 'bin';
748
    my $marionette_test = $test->{marionette_test} // $test->{name};
749
    my $pypath = $ENV{PYTHONPATH};
boklm's avatar
boklm committed
750 751 752 753
    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;
754 755 756
    $test->{screenshots} = [];
    my $screenshots_tmp = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
    $ENV{'MARIONETTE_SCREENSHOTS'} = winpath($screenshots_tmp);
757 758 759 760 761
    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}),
762
        $OSNAME eq 'cygwin' ? () : ('--workspace', $test->{workspace}),
763
        winpath("$FindBin::Bin/marionette/tor_browser_tests/test_${marionette_test}.py"));
764
    $ENV{PYTHONPATH} = $pypath;
765 766 767 768
    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;
769 770 771 772 773 774 775
    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++;
    }
776 777 778 779
    reset_test_prefs($tbbinfos, $test);
    parse_strace($tbbinfos, $test);
    check_opened_connections($tbbinfos, $test);
    check_modified_files($tbbinfos, $test);
780
    clean_strace($tbbinfos, $test);
781 782
}

783 784
sub set_tbbpaths {
    my ($tbbinfos) = @_;
785 786 787
    $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/firefox";
    $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/TorBrowser/Tor";
    $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/TorBrowser/Data";
boklm's avatar
boklm committed
788 789
    if ($tbbinfos->{os} eq 'MacOSX') {
        $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/Contents/MacOS/firefox";
790 791 792 793 794 795 796 797 798
        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
799
    }
800 801
    $tbbinfos->{torrcdefaults} //= "$tbbinfos->{datadir}/Tor/torrc-defaults";
    $tbbinfos->{torgeoip} //= "$tbbinfos->{datadir}/Tor/geoip";
802
    $tbbinfos->{torbin} = "$tbbinfos->{tordir}/tor";
boklm's avatar
boklm committed
803
    $tbbinfos->{ptdir} = winpath("$tbbinfos->{tordir}/PluggableTransports");
804
    $tbbinfos->{ffprofiledir} //= "$tbbinfos->{datadir}/Browser/profile.default";
805 806
}

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

816 817 818 819 820 821 822 823 824 825
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);
826 827 828 829
    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;
830
    chdir $tbbinfos->{tbbdir} || exit_error "Can't enter directory $tbbinfos->{tbbdir}";
boklm's avatar
boklm committed
831
    copy "$FindBin::Bin/data/cert_override.txt",
boklm's avatar
boklm committed
832
          "$tbbinfos->{ffprofiledir}/cert_override.txt";
833
    $ENV{TOR_SKIP_LAUNCH} = 1;
834 835
    $ENV{TOR_SOCKS_PORT} = $options->{'tor-socks-port'};
    $ENV{TOR_CONTROL_PORT} = $options->{'tor-control-port'};
boklm's avatar
boklm committed
836 837 838
    if ($options->{xdummy}) {
        $tbbinfos->{Xdisplay} = start_X("$tbbinfos->{'results-dir'}/xorg.log");
    }
839 840 841 842 843
}

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

1;