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 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
);

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

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
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
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
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;
}

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

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

sub check_modified_files {
    my ($tbbinfos, $test) = @_;
607
    my @bad_modified_files = @{$test->{results}{modified_files}};
608 609 610 611
    if (@bad_modified_files) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
612
    $test->{clean_strace} //= !@bad_modified_files;
613 614 615
    $test->{results}{bad_modified_files} = \@bad_modified_files;
}

616 617
sub clean_strace {
    my ($tbbinfos, $test) = @_;
618
    return unless $test->{clean_strace};
619 620
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    unlink $logfile;
boklm's avatar
boklm committed
621
    unlink "$logfile.tmp";
622 623
}

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

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

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

726 727 728 729 730 731 732 733 734 735 736 737
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;
}

738 739 740 741 742 743 744
sub marionette_run {
    my ($tbbinfos, $test) = @_;
    if ($test->{tried} && $test->{use_net}) {
        TBBTestSuite::Tests::TorBootstrap::send_newnym($tbbinfos);
    }
    set_test_prefs($tbbinfos, $test);

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

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

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

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

sub post_tests {
    my ($tbbinfos) = @_;
    TBBTestSuite::Tests::TorBootstrap::stop_tor($tbbinfos);
boklm's avatar
boklm committed
849
    stop_X($tbbinfos->{Xdisplay}) if $options->{xdummy};
850 851 852
}

1;