1#
2# This file is part of the LibreOffice project.
3#
4# This Source Code Form is subject to the terms of the Mozilla Public
5# License, v. 2.0. If a copy of the MPL was not distributed with this
6# file, You can obtain one at http://mozilla.org/MPL/2.0/.
7#
8# This file incorporates work covered by the following license notice:
9#
10#   Licensed to the Apache Software Foundation (ASF) under one or more
11#   contributor license agreements. See the NOTICE file distributed
12#   with this work for additional information regarding copyright
13#   ownership. The ASF licenses this file to you under the Apache
14#   License, Version 2.0 (the "License"); you may not use this file
15#   except in compliance with the License. You may obtain a copy of
16#   the License at http://www.apache.org/licenses/LICENSE-2.0 .
17#
18
19package installer::windows::directory;
20
21use installer::exiter;
22use installer::files;
23use installer::globals;
24use installer::pathanalyzer;
25use installer::windows::idtglobal;
26use installer::windows::msiglobal;
27
28##############################################################
29# Collecting all directory trees in global hash
30##############################################################
31
32my @msistandarddirectorynames = qw(
33   AdminToolsFolder
34   AppDataFolder
35   CommonAppDataFolder
36   CommonFiles64Folder
37   CommonFilesFolder
38   DesktopFolder
39   FavoritesFolder
40   FontsFolder
41   LocalAppDataFolder
42   MyPicturesFolder
43   NetHoodFolder
44   PersonalFolder
45   PrintHoodFolder
46   ProgramFiles64Folder
47   ProgramFilesFolder
48   ProgramMenuFolder
49   RecentFolder
50   SendToFolder
51   StartMenuFolder
52   StartupFolder
53   System16Folder
54   System64Folder
55   SystemFolder
56   TempFolder
57   TemplateFolder
58   WindowsFolder
59   WindowsVolume
60);
61
62sub collectdirectorytrees
63{
64    my ( $directoryref ) = @_;
65
66    for ( my $i = 0; $i <= $#{$directoryref}; $i++ )
67    {
68        my $onedir = ${$directoryref}[$i];
69        my $styles = "";
70        if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; }
71
72        if ( $styles ne "" )
73        {
74            foreach my $treestyle ( keys %installer::globals::treestyles )
75            {
76                if ( $styles =~ /\b$treestyle\b/ )
77                {
78                    my $hostname = $onedir->{'HostName'};
79                    # -> hostname is the key, the style the value!
80                    $installer::globals::hostnametreestyles{$hostname} = $treestyle;
81                }
82            }
83        }
84    }
85}
86
87##############################################################
88# Overwriting global programfilesfolder, if required
89##############################################################
90
91sub overwrite_programfilesfolder
92{
93    my ( $allvariables ) = @_;
94
95    if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 ))
96    {
97        $installer::globals::programfilesfolder = "ProgramFiles64Folder";
98    }
99}
100
101##############################################################
102# Maximum length of directory name is 72.
103# Taking care of underlines, which are the separator.
104##############################################################
105
106sub make_short_dir_version
107{
108    my ($longstring) = @_;
109
110    my $shortstring = "";
111    my $cutlength = 60;
112    my $length = 5; # So the directory can still be recognized
113    my $longstring_save = $longstring;
114
115    # Splitting the string at each "underline" and allowing only
116    # $length characters per directory name.
117    # Checking also uniqueness and length.
118
119    for my $onestring ( split /_\s*/, $longstring )
120    {
121        my $partstring = "";
122
123        if ( $onestring =~ /\-/ )
124        {
125            for my $onelocalstring ( split /-\s*/, $onestring )
126            {
127                if ( length($onelocalstring) > $length ) {
128                    $onelocalstring = substr($onelocalstring, 0, $length);
129                }
130                $partstring .= "-" . $onelocalstring;
131            }
132            $partstring =~ s/^\s*\-//;
133        }
134        else
135        {
136            if ( length($onestring) > $length ) {
137                $partstring = substr($onestring, 0, $length);
138            }
139            else {
140                $partstring = $onestring;
141            }
142        }
143
144        $shortstring .= "_" . $partstring;
145    }
146
147    $shortstring =~ s/^\s*\_//;
148
149    # Setting unique ID to each directory
150    # No counter allowed, process must be absolute reproducible due to patch creation process.
151
152    # chomp(my $id = `echo $longstring_save | md5sum | sed -e "s/ .*//g"`);  # Very, very slow
153    # my $subid = substr($id, 0, 9); # taking only the first 9 digits
154
155    my $subid = installer::windows::msiglobal::calculate_id($longstring_save, 9); # taking only the first 9 digits
156
157    if ( length($shortstring) > $cutlength ) { $shortstring = substr($shortstring, 0, $cutlength); }
158
159    $shortstring = $shortstring . "_" . $subid;
160
161    return $shortstring;
162}
163
164##############################################################
165# Adding unique directory names to the directory collection
166##############################################################
167
168my $already_checked_the_frigging_directories_for_uniqueness = 0;
169
170sub create_unique_directorynames
171{
172    my ($directoryref, $allvariables) = @_;
173
174    my %completedirhashstep1 = ();
175    my %shortdirhash = ();
176    my %shortdirhashreverse = ();
177    my $infoline = "";
178
179    for ( my $i = 0; $i <= $#{$directoryref}; $i++ )
180    {
181        my $onedir = ${$directoryref}[$i];
182        my $uniquename = $onedir->{'HostName'};
183
184        my $styles = "";
185        if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; }
186
187        $uniquename =~ s/^\s*//g;               # removing beginning white spaces
188        $uniquename =~ s/\s*$//g;               # removing ending white spaces
189        $uniquename =~ s/\s//g;                 # removing white spaces
190        $uniquename =~ s/\_//g;                 # removing existing underlines
191        $uniquename =~ s/\.//g;                 # removing dots in directoryname
192        $uniquename =~ s/\Q$installer::globals::separator\E/\_/g;   # replacing slash and backslash with underline
193        $uniquename =~ s/OpenOffice/OO/g;
194        $uniquename =~ s/LibreOffice/LO/g;
195        $uniquename =~ s/_registry/_rgy/g;
196        $uniquename =~ s/_registration/_rgn/g;
197        $uniquename =~ s/_extension/_ext/g;
198        $uniquename =~ s/_frame/_frm/g;
199        $uniquename =~ s/_table/_tbl/g;
200        $uniquename =~ s/_chart/_crt/g;
201        $uniquename =~ s/_plat-linux/_plx/g;
202
203        # The names after this small changes must still be unique!
204        if ( exists($completedirhashstep1{$uniquename}) ) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 1): \"$uniquename\".", "create_unique_directorynames"); }
205        $completedirhashstep1{$uniquename} = 1;
206
207        # Starting to make unique name for the parent and its directory
208        my $originaluniquename = $uniquename;
209
210        $uniquename = make_short_dir_version($uniquename);
211
212        # Checking if the same directory already exists, but has another short version.
213        if (( exists($shortdirhash{$originaluniquename}) ) && ( $shortdirhash{$originaluniquename} ne $uniquename )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 2A): \"$uniquename\".", "create_unique_directorynames"); }
214
215        # Also checking vice versa
216        # Checking if the same short directory already exists, but has another long version.
217        if (( exists($shortdirhashreverse{$uniquename}) ) && ( $shortdirhashreverse{$uniquename} ne $originaluniquename )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 2B): \"$uniquename\".", "create_unique_directorynames"); }
218
219        # Creating assignment from long to short directory names
220        $shortdirhash{$originaluniquename} = $uniquename;
221        $shortdirhashreverse{$uniquename} = $originaluniquename;
222
223        # Important: The unique parent is generated from the string $originaluniquename (with the use of underlines).
224
225        my $uniqueparentname = $originaluniquename;
226        my $keepparent = 1;
227
228        if ( $uniqueparentname =~ /^\s*(.*)\_(.*?)\s*$/ )   # the underline is now the separator
229        {
230            $uniqueparentname = $1;
231            $keepparent = 0;
232        }
233        else
234        {
235            $uniqueparentname = $installer::globals::programfilesfolder;
236            $keepparent = 1;
237        }
238
239        if ( $styles =~ /\bPROGRAMFILESFOLDER\b/ )
240        {
241            $uniqueparentname = $installer::globals::programfilesfolder;
242            $keepparent = 1;
243        }
244        if ( $styles =~ /\bCOMMONFILESFOLDER\b/ )
245        {
246            $uniqueparentname = $installer::globals::commonfilesfolder;
247            $keepparent = 1;
248        }
249        if ( $styles =~ /\bCOMMONAPPDATAFOLDER\b/ )
250        {
251            $uniqueparentname = $installer::globals::commonappdatafolder;
252            $keepparent = 1;
253        }
254        if ( $styles =~ /\bLOCALAPPDATAFOLDER\b/ )
255        {
256            $uniqueparentname = $installer::globals::localappdatafolder;
257            $keepparent = 1;
258        }
259
260        if ( $styles =~ /\bSHAREPOINTPATH\b/ )
261        {
262            $uniqueparentname = "SHAREPOINTPATH";
263            $installer::globals::usesharepointpath = 1;
264            $keepparent = 1;
265        }
266
267        # also setting short directory name for the parent
268
269        my $originaluniqueparentname = $uniqueparentname;
270
271        if ( ! $keepparent )
272        {
273            $uniqueparentname = make_short_dir_version($uniqueparentname);
274        }
275
276        # Again checking if the same directory already exists, but has another short version.
277        if (( exists($shortdirhash{$originaluniqueparentname}) ) && ( $shortdirhash{$originaluniqueparentname} ne $uniqueparentname )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 3A): \"$uniqueparentname\".", "create_unique_directorynames"); }
278
279        # Also checking vice versa
280        # Checking if the same short directory already exists, but has another long version.
281        if (( exists($shortdirhashreverse{$uniqueparentname}) ) && ( $shortdirhashreverse{$uniqueparentname} ne $originaluniqueparentname )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 3B): \"$uniqueparentname\".", "create_unique_directorynames"); }
282
283        $shortdirhash{$originaluniqueparentname} = $uniqueparentname;
284        $shortdirhashreverse{$uniqueparentname} = $originaluniqueparentname;
285
286        # Hyphen not allowed in database
287        $uniquename =~ s/\-/\_/g;           # making "-" to "_"
288        $uniqueparentname =~ s/\-/\_/g;     # making "-" to "_"
289
290        # And finally setting the values for the directories
291        $onedir->{'uniquename'} = $uniquename;
292        $onedir->{'uniqueparentname'} = $uniqueparentname;
293
294        # setting the installlocation directory
295        if ( $styles =~ /\bISINSTALLLOCATION\b/ )
296        {
297            if ( $installer::globals::installlocationdirectoryset ) { installer::exiter::exit_program("ERROR: Directory with flag ISINSTALLLOCATION already set: \"$installer::globals::installlocationdirectory\".", "create_unique_directorynames"); }
298            $installer::globals::installlocationdirectory = $uniquename;
299            $installer::globals::installlocationdirectoryset = 1;
300        }
301    }
302}
303
304#####################################################
305# Adding ":." to selected default directory names
306#####################################################
307
308sub check_sourcedir_addon
309{
310    my ( $onedir, $allvariableshashref ) = @_;
311
312    if (($installer::globals::languagepack) ||
313        ($installer::globals::helppack) ||
314        ($allvariableshashref->{'CHANGETARGETDIR'}))
315    {
316        my $sourcediraddon = "\:\.";
317        $onedir->{'defaultdir'} = $onedir->{'defaultdir'} . $sourcediraddon;
318    }
319
320}
321
322#####################################################
323# The directory with the style ISINSTALLLOCATION
324# will be replaced by INSTALLLOCATION
325#####################################################
326
327sub set_installlocation_directory
328{
329    my ( $directoryref, $allvariableshashref ) = @_;
330
331    if ( ! $installer::globals::installlocationdirectoryset ) { installer::exiter::exit_program("ERROR: Directory with flag ISINSTALLLOCATION not set!", "set_installlocation_directory"); }
332
333    for ( my $i = 0; $i <= $#{$directoryref}; $i++ )
334    {
335        my $onedir = ${$directoryref}[$i];
336
337        if ( $onedir->{'uniquename'} eq $installer::globals::installlocationdirectory )
338        {
339            $onedir->{'uniquename'} = "INSTALLLOCATION";
340            check_sourcedir_addon($onedir, $allvariableshashref);
341        }
342
343        if ( $onedir->{'uniquename'} eq $installer::globals::vendordirectory )
344        {
345            check_sourcedir_addon($onedir, $allvariableshashref);
346        }
347
348        if ( $onedir->{'uniqueparentname'} eq $installer::globals::installlocationdirectory )
349        {
350            $onedir->{'uniqueparentname'} = "INSTALLLOCATION";
351        }
352    }
353}
354
355#####################################################
356# Getting the name of the top level directory. This
357# can have only one letter
358#####################################################
359
360sub get_last_directory_name
361{
362    my ($completepathref) = @_;
363
364    if ( $$completepathref =~ /^.*[\/\\](.+?)\s*$/ )
365    {
366        $$completepathref = $1;
367    }
368}
369
370#####################################################
371# Creating the defaultdir for the file Director.idt
372#####################################################
373
374sub create_defaultdir_directorynames
375{
376    my ($directoryref, $shortdirnamehashref) = @_;
377
378    my @shortnames = ();
379    if ( $installer::globals::updatedatabase ) { @shortnames = values(%{$shortdirnamehashref}); }
380    elsif ( $installer::globals::prepare_winpatch ) { @shortnames = values(%installer::globals::saved83dirmapping); }
381
382    for ( my $i = 0; $i <= $#{$directoryref}; $i++ )
383    {
384        my $onedir = ${$directoryref}[$i];
385        my $hostname = $onedir->{'HostName'};
386
387        $hostname =~ s/\Q$installer::globals::separator\E\s*$//;
388        get_last_directory_name(\$hostname);
389        my $uniquename = $onedir->{'uniquename'};
390        my $shortstring;
391        if (( $installer::globals::updatedatabase ) && ( exists($shortdirnamehashref->{$uniquename}) ))
392        {
393            $shortstring = $shortdirnamehashref->{$uniquename};
394        }
395        elsif (( $installer::globals::prepare_winpatch ) && ( exists($installer::globals::saved83dirmapping{$uniquename}) ))
396        {
397            $shortstring = $installer::globals::saved83dirmapping{$uniquename};
398        }
399        else
400        {
401            $shortstring = installer::windows::idtglobal::make_eight_three_conform($hostname, "dir", \@shortnames);
402        }
403
404        my $defaultdir;
405
406        if ( $shortstring eq $hostname )
407        {
408            $defaultdir = $hostname;
409        }
410        else
411        {
412            $defaultdir = $shortstring . "|" . $hostname;
413        }
414
415        $onedir->{'defaultdir'} = $defaultdir;
416
417        my $fontdir = "";
418        if ( $onedir->{'Dir'} ) { $fontdir = $onedir->{'Dir'}; }
419
420        my $fontdefaultdir = "";
421        if ( $onedir->{'defaultdir'} ) { $fontdefaultdir = $onedir->{'defaultdir'}; }
422
423        if (( $fontdir eq $installer::globals::fontsdirhostname ) && ( $fontdefaultdir eq $installer::globals::fontsdirhostname ))
424        {
425            $installer::globals::fontsdirname = $onedir->{'defaultdir'};
426            $installer::globals::fontsdirparent = $onedir->{'uniqueparentname'};
427        }
428    }
429}
430
431###############################################
432# Fill content into the directory table
433###############################################
434
435sub create_directorytable_from_collection
436{
437    my ($directorytableref, $directoryref) = @_;
438
439    for ( my $i = 0; $i <= $#{$directoryref}; $i++ )
440    {
441        my $onedir = ${$directoryref}[$i];
442        my $hostname = $onedir->{'HostName'};
443        my $dir = "";
444
445        if ( $onedir->{'Dir'} ) { $dir = $onedir->{'Dir'}; }
446
447        if (( $dir eq "PREDEFINED_PROGDIR" ) && ( $hostname eq "" )) { next; }  # removing files from root directory
448
449        my $oneline = $onedir->{'uniquename'} . "\t" . $onedir->{'uniqueparentname'} . "\t" . $onedir->{'defaultdir'} . "\n";
450
451        push(@{$directorytableref}, $oneline);
452    }
453}
454
455###############################################
456# Defining the root installation structure
457###############################################
458
459sub add_root_directories
460{
461    my ($directorytableref, $allvariableshashref, $onelanguage) = @_;
462
463    my $oneline = "";
464
465    if (( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack ) && ( ! $allvariableshashref->{'DONTUSESTARTMENUFOLDER'} ))
466    {
467        my $productname;
468
469    $productname = $allvariableshashref->{'PRODUCTNAME'};
470        my $productversion = $allvariableshashref->{'PRODUCTVERSION'};
471        my $baseproductversion = $productversion;
472
473        if (( $installer::globals::prepare_winpatch ) && ( $allvariableshashref->{'BASEPRODUCTVERSION'} ))
474        {
475            $baseproductversion = $allvariableshashref->{'BASEPRODUCTVERSION'};  # for example "2.0" for OOo
476        }
477
478        my $realproductkey = $productname . " " . $productversion;
479        my $productkey = $productname . " " . $baseproductversion;
480
481        if (( $allvariableshashref->{'POSTVERSIONEXTENSION'} ) && ( ! $allvariableshashref->{'DONTUSEEXTENSIONINDEFAULTDIR'} ))
482        {
483            $productkey = $productkey . " " . $allvariableshashref->{'POSTVERSIONEXTENSION'};
484            $realproductkey = $realproductkey . " " . $allvariableshashref->{'POSTVERSIONEXTENSION'};
485        }
486        if ( $allvariableshashref->{'NOVERSIONINDIRNAME'} )
487        {
488            $productkey = $productname;
489            $realproductkey = $realproductname;
490        }
491        if ( $allvariableshashref->{'NOSPACEINDIRECTORYNAME'} )
492        {
493            $productkey =~ s/\ /\_/g;
494            $realproductkey =~ s/\ /\_/g;
495        }
496
497        my $shortproductkey = installer::windows::idtglobal::make_eight_three_conform($productkey, "dir");      # third parameter not used
498        $shortproductkey =~ s/\s/\_/g;                                  # changing empty space to underline
499
500        $oneline = "$installer::globals::officemenufolder\t$installer::globals::programmenufolder\t$shortproductkey|$realproductkey\n";
501        push(@{$directorytableref}, $oneline);
502    }
503
504    $oneline = "TARGETDIR\t\tSourceDir\n";
505    push(@{$directorytableref}, $oneline);
506
507    $oneline = "WindowsFolder\tTARGETDIR\tWindows\n";
508    push(@{$directorytableref}, $oneline);
509
510    $oneline = "$installer::globals::programfilesfolder\tTARGETDIR\t.\n";
511    push(@{$directorytableref}, $oneline);
512
513    $oneline = "$installer::globals::programmenufolder\tTARGETDIR\t.\n";
514    push(@{$directorytableref}, $oneline);
515
516    $oneline = "$installer::globals::startupfolder\tTARGETDIR\t.\n";
517    push(@{$directorytableref}, $oneline);
518
519    $oneline = "$installer::globals::desktopfolder\tTARGETDIR\t.\n";
520    push(@{$directorytableref}, $oneline);
521
522    $oneline = "$installer::globals::startmenufolder\tTARGETDIR\t.\n";
523    push(@{$directorytableref}, $oneline);
524
525    $oneline = "$installer::globals::commonfilesfolder\tTARGETDIR\t.\n";
526    push(@{$directorytableref}, $oneline);
527
528    $oneline = "$installer::globals::commonappdatafolder\tTARGETDIR\t.\n";
529    push(@{$directorytableref}, $oneline);
530
531    $oneline = "$installer::globals::localappdatafolder\tTARGETDIR\t.\n";
532    push(@{$directorytableref}, $oneline);
533
534    if ( $installer::globals::usesharepointpath )
535    {
536        $oneline = "SHAREPOINTPATH\tTARGETDIR\t.\n";
537        push(@{$directorytableref}, $oneline);
538    }
539
540    my $localtemplatefoldername = $installer::globals::templatefoldername;
541    my $directorytableentry = $localtemplatefoldername;
542    my $shorttemplatefoldername = installer::windows::idtglobal::make_eight_three_conform($localtemplatefoldername, "dir");
543    if ( $shorttemplatefoldername ne $localtemplatefoldername ) { $directorytableentry = "$shorttemplatefoldername|$localtemplatefoldername"; }
544    $oneline = "$installer::globals::templatefolder\tTARGETDIR\t$directorytableentry\n";
545    push(@{$directorytableref}, $oneline);
546
547    if ( $installer::globals::fontsdirname )
548    {
549        $oneline = "$installer::globals::fontsfolder\t$installer::globals::fontsdirparent\t$installer::globals::fontsfoldername\:$installer::globals::fontsdirname\n";
550    }
551    else
552    {
553        $oneline = "$installer::globals::fontsfolder\tTARGETDIR\t$installer::globals::fontsfoldername\n";
554    }
555
556    push(@{$directorytableref}, $oneline);
557
558}
559
560###############################################
561# Creating the file Director.idt dynamically
562###############################################
563
564sub create_directory_table
565{
566    my ($directoryref, $languagesarrayref, $basedir, $allvariableshashref, $shortdirnamehashref, $loggingdir) = @_;
567
568    # Structure of the directory table:
569    # Directory Directory_Parent DefaultDir
570    # Directory is a unique identifier
571    # Directory_Parent is the unique identifier of the parent
572    # DefaultDir is .:APPLIC~1|Application Data with
573    # Before ":" : [sourcedir]:[destdir] (not programmed yet)
574    # After ":" : 8+3 and not 8+3 the destination directory name
575
576    for ( my $m = 0; $m <= $#{$languagesarrayref}; $m++ )
577    {
578        my $onelanguage = ${$languagesarrayref}[$m];
579        $installer::globals::installlocationdirectoryset = 0;
580
581    my @directorytable = ();
582    my $infoline;
583
584    overwrite_programfilesfolder($allvariableshashref);
585    create_unique_directorynames($directoryref, $allvariableshashref);
586    $already_checked_the_frigging_directories_for_uniqueness++;
587    create_defaultdir_directorynames($directoryref, $shortdirnamehashref);  # only destdir!
588    set_installlocation_directory($directoryref, $allvariableshashref);
589    installer::windows::idtglobal::write_idt_header(\@directorytable, "directory");
590    add_root_directories(\@directorytable, $allvariableshashref, $onelanguage);
591    create_directorytable_from_collection(\@directorytable, $directoryref);
592
593    # Saving the file
594
595    my $directorytablename = $basedir . $installer::globals::separator . "Director.idt" . "." . $onelanguage;
596    installer::files::save_file($directorytablename ,\@directorytable);
597    $infoline = "Created idt file: $directorytablename\n";
598    push(@installer::globals::logfileinfo, $infoline);
599    }
600}
601
602################################################
603# Check if the string starts with another string
604################################################
605
606sub starts_with
607{
608    my ($first, $second) = @_;
609
610    return substr($first, 0, length($second)) eq $second;
611}
612
613###############################################
614# Check if the directory prefix is a standard
615# directory name. If it is the case, then the
616# standard directory name is returned in $var.
617###############################################
618
619sub has_standard_directory_prefix
620{
621    my ($dir, $var) = @_;
622
623    for my $d (@msistandarddirectorynames) {
624        if (starts_with($dir, $d) && $dir ne $d) {
625            installer::logger::print_message("... match found: [$d]\n");
626            ${$var} = $d;
627            return 1;
628        }
629    }
630
631    return 0;
632}
633
6341;
635