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.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
    {
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
    {
        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',
        #},
    },
350
    {
boklm's avatar
boklm committed
351
        name            => 'searchengines',
352
        type            => 'marionette',
boklm's avatar
boklm committed
353
        descr           => 'Check that we have the default search engines set',
354
    },
boklm's avatar
boklm committed
355
    {
boklm's avatar
boklm committed
356
        name            => 'noscript',
357
        type            => 'marionette',
boklm's avatar
boklm committed
358 359 360
        descr           => 'Check that noscript options are working',
        use_net         => 1,
        prefs           => {
361
            'extensions.torbutton.security_slider' => 2,
boklm's avatar
boklm committed
362
        },
boklm's avatar
boklm committed
363
        enable          => sub { $_[0]->{version} !~ m/^4.0/ },
boklm's avatar
boklm committed
364
    },
boklm's avatar
boklm committed
365
    {
boklm's avatar
boklm committed
366
        name            => 'fp_screen_dimensions',
367
        type            => 'marionette',
boklm's avatar
boklm committed
368
        descr           => 'Check that screen dimensions are spoofed correctly',
369 370
    },
    {
boklm's avatar
boklm committed
371
        name            => 'fp_screen_coords',
372
        type            => 'marionette',
boklm's avatar
boklm committed
373
        descr           => 'Check that screenX, screenY, screenLeft, screenTop, mozInnerScreenX, mozInnerScreenY are 0',
374 375
    },
    {
boklm's avatar
boklm committed
376
        name            => 'fp_plugins',
377
        type            => 'marionette',
boklm's avatar
boklm committed
378
        descr           => 'Check that plugins are disabled',
379 380
    },
    {
boklm's avatar
boklm committed
381
        name            => 'fp_useragent',
382
        type            => 'marionette',
boklm's avatar
boklm committed
383
        descr           => 'Check that userAgent is as expected',
384 385
    },
    {
boklm's avatar
boklm committed
386
        name            => 'fp_navigator',
387
        type            => 'marionette',
boklm's avatar
boklm committed
388 389 390 391
        descr           => 'Check that navigator properties are as expected',
    },
    {
        name            => 'play_videos',
392
        type            => 'marionette',
boklm's avatar
boklm committed
393 394
        descr           => 'Play some videos',
        use_net         => 1,
395
        marionette_test => 'page',
boklm's avatar
boklm committed
396 397 398 399 400
        remote          => 1,
        timeout         => 50000,
    },
    {
        name            => 'svg-disable',
boklm's avatar
boklm committed
401
        type            => 'marionette',
boklm's avatar
boklm committed
402
        descr           => 'Check if disabling svg is working',
boklm's avatar
boklm committed
403
        marionette_test => 'svg',
boklm's avatar
boklm committed
404 405
        use_net         => 1,
        prefs           => {
406 407 408
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'false',
        },
boklm's avatar
boklm committed
409
        enable          => sub { $OSNAME eq 'linux' },
410 411
    },
    {
boklm's avatar
boklm committed
412
        name            => 'svg-enable',
boklm's avatar
boklm committed
413
        type            => 'marionette',
boklm's avatar
boklm committed
414
        descr           => 'Check if enabling svg is working',
boklm's avatar
boklm committed
415
        marionette_test => 'svg',
boklm's avatar
boklm committed
416 417
        use_net         => 1,
        prefs           => {
418 419 420
            'extensions.torbutton.security_custom' => 'true',
            'svg.in-content.enabled' => 'true',
        },
boklm's avatar
boklm committed
421
        enable          => sub { $OSNAME eq 'linux' },
422
    },
423 424 425 426 427 428
);

sub toggle_https_everywhere {
    my ($tbbinfos, $t) = @_;
    my $prefs = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere@eff.org/defaults/preferences/preferences.js';
429 430 431
    my $prefs_eff = $tbbinfos->{ffprofiledir} . '/extensions/'
        . 'https-everywhere-eff@eff.org/defaults/preferences/preferences.js';
    $prefs = $prefs_eff unless -f $prefs;
432 433 434 435 436 437 438 439 440 441 442 443 444
    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);
}

445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
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
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
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;
}

479 480 481
sub tbb_binfiles {
    my ($tbbinfos, $test) = @_;
    return $tbbinfos->{binfiles} if $tbbinfos->{binfiles};
482
    my %binfiles;
483 484 485 486
    my %wanted_types = (
        'application/x-executable-file' => 1,
        'application/x-ms-dos-executable' => 1,
    );
487 488 489
    my $wanted = sub {
        return unless -f $File::Find::name;
        my $type = File::Type->new->checktype_filename($File::Find::name);
490
        return unless $wanted_types{$type};
boklm's avatar
boklm committed
491 492 493
        my $name = $File::Find::name;
        $name =~ s/^$tbbinfos->{tbbdir}\///;
        $binfiles{$name} = 1;
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
    };
    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);
532 533 534 535 536
        if ($tbbinfos->{language} eq 'ALL') {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser";
        } else {
            $tbbinfos->{tbbdir} = "$tmpdir/tor-browser_$tbbinfos->{language}";
        }
537
        $tbbinfos->{tbbdir} .= '/Browser';
538 539 540 541
    } elsif ($tbbinfos->{os} eq 'Windows') {
        my (undef, undef, $f) = File::Spec->splitpath($tbbfile);
        copy($tbbfile, "$tmpdir/$f");
        system('7z', 'x', $f);
542
        $tbbinfos->{tbbdir} = "$tmpdir/torbrowser/Browser";
543 544 545 546 547
        move("$tmpdir/\$_OUTDIR", "$tmpdir/torbrowser") if -d "$tmpdir/\$_OUTDIR";
        if (-d "$tmpdir/Browser") {
            mkdir "$tmpdir/torbrowser";
            move("$tmpdir/Browser", "$tmpdir/torbrowser/Browser");
        }
548
        move ("$tmpdir/Start Tor Browser.exe", "$tmpdir/torbrowser/");
boklm's avatar
boklm committed
549 550 551 552 553 554
    } 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";
555 556 557 558 559 560 561 562 563 564 565 566 567
    }
}

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) = @_;
568 569 570 571 572 573
    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
574 575 576 577
    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'}
    }
578 579 580 581
    if (%bad_connections) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
582
    $test->{clean_strace} //= !%bad_connections;
583
    $test->{results}{bad_connections} = \%bad_connections;
584 585 586 587
}

sub check_modified_files {
    my ($tbbinfos, $test) = @_;
588
    my @bad_modified_files = @{$test->{results}{modified_files}};
589 590 591 592
    if (@bad_modified_files) {
        $test->{results}{success} = 0;
        $test->{retry} = 0;
    }
593
    $test->{clean_strace} //= !@bad_modified_files;
594 595 596
    $test->{results}{bad_modified_files} = \@bad_modified_files;
}

597 598
sub clean_strace {
    my ($tbbinfos, $test) = @_;
599
    return unless $test->{clean_strace};
600 601
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
    unlink $logfile;
boklm's avatar
boklm committed
602
    unlink "$logfile.tmp";
603 604
}

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

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
659
export HOME="$tbbinfos->{tbbdir}"
660
export LD_LIBRARY_PATH="$tbbinfos->{tbbdir}:$tbbinfos->{tordir}"
661 662 663 664 665 666 667
exec \'$tbbinfos->{ffbin}\' "\$@"
EOF
    write_file($wrapper_file, $wrapper);
    chmod 0700, $wrapper_file;
    return $wrapper_file;
}

668
sub ff_strace_wrapper {
669 670
    my ($tbbinfos, $test) = @_;
    my $ff_wrapper = ff_wrapper($tbbinfos, $test);
671
    my $logfile = "$tbbinfos->{'results-dir'}/$test->{name}.strace";
672 673
    my $wrapper = <<EOF;
#!/bin/sh
674 675 676 677 678 679
if [ -f $logfile.tmp ]
then
   cat $logfile.tmp >> $logfile
   rm $logfile.tmp
fi
echo \$@ >> /tmp/ff_run.log
680 681 682 683 684
strace -f -o $logfile.tmp -- \'$ff_wrapper\' "\$@"
exit_code=\$?
cat $logfile.tmp >> $logfile
rm $logfile.tmp
exit \$?
685 686 687 688 689 690 691 692 693 694 695 696
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");
    }
697
    my %t = map { $_ => 1 } qw(marionette);
698
    if ($options->{use_strace} && $t{$test->{type}}) {
boklm's avatar
boklm committed
699 700
        return ff_strace_wrapper($tbbinfos, $test);
    }
boklm's avatar
boklm committed
701
    return $tbbinfos->{ffbin} if $OSNAME eq 'darwin';
702
    return ff_wrapper($tbbinfos, $test);
703 704
}

705 706 707 708 709 710 711 712 713 714 715 716
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;
}

717 718 719 720 721 722 723
sub marionette_run {
    my ($tbbinfos, $test) = @_;
    if ($test->{tried} && $test->{use_net}) {
        TBBTestSuite::Tests::TorBootstrap::send_newnym($tbbinfos);
    }
    set_test_prefs($tbbinfos, $test);

724 725
    my $options_file = marionette_export_options($tbbinfos, $test);
    $ENV{TESTSUITE_DATA_FILE} = winpath($options_file);
726 727
    my $result_file_html = "$tbbinfos->{'results-dir'}/$test->{name}.html";
    my $result_file_txt = "$tbbinfos->{'results-dir'}/$test->{name}.txt";
728 729
    $test->{workspace} = "$tbbinfos->{'results-dir'}/$test->{name}_ws";
    mkdir $test->{workspace};
730 731
    #--log-unittest  ./res.txt --log-html ./res.html
    my $bin = $OSNAME eq 'cygwin' ? 'Scripts' : 'bin';
732
    my $marionette_test = $test->{marionette_test} // $test->{name};
733
    my $pypath = $ENV{PYTHONPATH};
boklm's avatar
boklm committed
734 735 736 737
    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;
738 739 740
    $test->{screenshots} = [];
    my $screenshots_tmp = File::Temp::newdir('XXXXXX', DIR => $options->{tmpdir});
    $ENV{'MARIONETTE_SCREENSHOTS'} = winpath($screenshots_tmp);
741 742 743 744 745
    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}),
746
        '--workspace', winpath($test->{workspace}),
747
        winpath("$FindBin::Bin/marionette/tor_browser_tests/test_${marionette_test}.py"));
748
    $ENV{PYTHONPATH} = $pypath;
749 750 751 752
    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;
753 754 755 756 757 758 759
    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++;
    }
760 761 762 763
    reset_test_prefs($tbbinfos, $test);
    parse_strace($tbbinfos, $test);
    check_opened_connections($tbbinfos, $test);
    check_modified_files($tbbinfos, $test);
764
    clean_strace($tbbinfos, $test);
765 766
}

767 768
sub set_tbbpaths {
    my ($tbbinfos) = @_;
769 770 771
    $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/firefox";
    $tbbinfos->{tordir} = "$tbbinfos->{tbbdir}/TorBrowser/Tor";
    $tbbinfos->{datadir} = "$tbbinfos->{tbbdir}/TorBrowser/Data";
boklm's avatar
boklm committed
772 773
    if ($tbbinfos->{os} eq 'MacOSX') {
        $tbbinfos->{ffbin} = "$tbbinfos->{tbbdir}/Contents/MacOS/firefox";
774 775 776 777 778 779 780 781 782
        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
783
    }
784 785
    $tbbinfos->{torrcdefaults} //= "$tbbinfos->{datadir}/Tor/torrc-defaults";
    $tbbinfos->{torgeoip} //= "$tbbinfos->{datadir}/Tor/geoip";
786
    $tbbinfos->{torbin} = "$tbbinfos->{tordir}/tor";
boklm's avatar
boklm committed
787
    $tbbinfos->{ptdir} = winpath("$tbbinfos->{tordir}/PluggableTransports");
788
    $tbbinfos->{ffprofiledir} //= "$tbbinfos->{datadir}/Browser/profile.default";
789 790
}

boklm's avatar
boklm committed
791 792 793 794
sub new {
    my ($ts, $testsuite) = @_;
    $testsuite->{type} = 'browserbundle';
    $testsuite->{tests} = [ map { { %$_ } } @tests ];
795 796
    return undef unless $testsuite->{os} eq $options->{os};
    return undef unless $testsuite->{arch} eq $options->{arch};
boklm's avatar
boklm committed
797 798 799
    return bless $testsuite, $ts;
}

800 801 802 803 804 805 806 807 808 809
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);
810 811 812 813
    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;
814
    chdir $tbbinfos->{tbbdir} || exit_error "Can't enter directory $tbbinfos->{tbbdir}";
boklm's avatar
boklm committed
815
    copy "$FindBin::Bin/data/cert_override.txt",
boklm's avatar
boklm committed
816
          "$tbbinfos->{ffprofiledir}/cert_override.txt";
817
    $ENV{TOR_SKIP_LAUNCH} = 1;
818 819
    $ENV{TOR_SOCKS_PORT} = $options->{'tor-socks-port'};
    $ENV{TOR_CONTROL_PORT} = $options->{'tor-control-port'};
boklm's avatar
boklm committed
820 821 822
    if ($options->{xdummy}) {
        $tbbinfos->{Xdisplay} = start_X("$tbbinfos->{'results-dir'}/xorg.log");
    }
823 824 825 826 827
}

sub post_tests {
    my ($tbbinfos) = @_;
    TBBTestSuite::Tests::TorBootstrap::stop_tor($tbbinfos);
boklm's avatar
boklm committed
828
    stop_X($tbbinfos->{Xdisplay}) if $options->{xdummy};
829 830 831
}

1;