Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / perl / CXMimeMcap.pm

# (c) Copyright 2005-2008, 2010, 2012, 2014. CodeWeavers, Inc.
package CXMimeMcap;
use warnings;
use strict;

use CXLog;
use CXUtils;
use CXAssoc;
use base "CXAssoc";

#####
#
# Package for reading and writing mime.types and mailcap files
# See RFC 1524 for the format of the Mailcap file
#
#####

{
    package CXRWBlob;
    use strict;

    use CXLog;
    use CXUtils;

    sub get($$)
    {
        my ($self, $mimetype)=@_;
        my $blob=$self->{mimetypes}->{$mimetype};
        return undef if (!$blob);
        return undef if ($blob->{state} eq "deleted");
        return $blob;
    }

    sub get_created_by($$$)
    {
        my ($self, $domain, $mimetype)=@_;
        my $blob=$self->{created_by}->{$mimetype}->{$domain};
        return undef if (!$blob);
        return undef if ($blob->{state} eq "deleted");
        return $blob;
    }

    sub is_crossover_mimetype($$)
    {
        my ($self, $mimetype)=@_;
        return 1 if ($self->{all_created_by}->{$mimetype});
        return 0;
    }

    sub modified($$)
    {
        my ($self, $blob)=@_;
        $blob->{state}="modified";
        $self->{modified}=1;
    }

    sub add($$$;$)
    {
        my ($self, $type, $mimetype, $domain)=@_;
        my $blob;
        if ($type eq "created-by")
        {
            $blob=$self->get_created_by($domain, $mimetype);
        }
        else
        {
            $blob=$self->get($mimetype);
        }
        if (!$blob)
        {
            $blob={
                type        => $type,
                mimetype    => $mimetype,
                state       => "up-to-date"
            };
            if ($type eq "created-by")
            {
                $self->{created_by}->{$mimetype}->{$domain}=$blob;
                $blob->{domain}=$domain;
                $blob->{tag}=$self->{tag};
                my $mime=$self->{mimetypes}->{$mimetype};
                if ($mime)
                {
                    # Add this comment next to the related MIME type
                    my $blobs=$self->{blobs};
                    my $i=$mime->{line};
                    while ($blobs->[$i])
                    {
                        if ($blobs->[$i] == $mime)
                        {
                            $i++;
                            last;
                        }
                        $i++;
                    }
                    splice @$blobs, $i, 0, $blob;
                    $blob->{line}=$i;
                }
                else
                {
                    $blob->{line}=scalar(@{$self->{blobs}});
                    push @{$self->{blobs}}, $blob;
                }
            }
            else
            {
                $self->{mimetypes}->{$mimetype}=$blob;
                $blob->{line}=scalar(@{$self->{blobs}});
                push @{$self->{blobs}}, $blob;
            }
            $self->{modified}=1;
        }
        elsif ($blob->{state} eq "deleted")
        {
            if ($type eq "created-by")
            {
                $self->{all_created_by}->{$mimetype}->{$blob->{domain}}->{$blob->{tag}}=$blob;
            }
            # It's up to the caller to clean up this blob
            $blob->{state}="modified";
        }
        if ($type eq "created-by" and $self->{type} eq "mailcap")
        {
            # For associations, make sure there's only one Created-By field
            # per MIME type
            foreach my $b (values %{$self->{all_created_by}->{$mimetype}->{$domain}})
            {
                $b->{state}="deleted" if ($b != $blob);
            }
            $self->{all_created_by}->{$mimetype}->{$blob->{domain}}={
                $blob->{tag} => $blob
            };
        }
        return $blob;
    }

    sub remove($$)
    {
        my ($self, $blob)=@_;
        $blob->{state}="deleted";
        $self->{modified}=1;
    }

    sub remove_created_by($$)
    {
        my ($self, $blob)=@_;
        $blob->{state}="deleted";
        $self->{modified}=1;

        my $all=$self->{all_created_by}->{$blob->{mimetype}};
        delete $all->{$blob->{domain}}->{$blob->{tag}};
        delete $all->{$blob->{domain}} if (!%{$all->{$blob->{domain}}});
        # Return true if the MIME type is still in use
        return %$all;
    }

    my %cxrwcache;

    sub new($$$$$)
    {
        my ($class, $tag, $filename, $type, $multiline_default)=@_;

        # Try to get the file from the cache first
        my $self;
        if (defined $filename)
        {
            $self=$cxrwcache{$filename};
            return $self if ($self);
        }

        $self={
            tag            => $tag,
            filename       => $filename,
            type           => $type,
            blobs          => [],    # Ordered list of the sections
            mimetypes      => {},    # For quick access to MIME type data
            created_by     => {},    # and to X-Created-By data
            all_created_by => {},
            multiline      => $multiline_default
        };
        bless $self, $class;

        if (defined $filename and -e $filename)
        {
            my $fh;
            return undef if (!open($fh, "<", $filename));

            my $count=0;
            while (my $line0=<$fh>)
            {
                chomp $line0;
                my $blob={
                    state => "up-to-date",
                    line  => $count,
                    lines => [ $line0 ]
                };
                push @{$self->{blobs}}, $blob;
                $count++;

                if ($line0 =~ /^\s*#\s*X-Created-By-([^:=-]*)-([^:=]*):([^= ]*)\s*=\s*(\S*)/)
                {
                    cxlog("x-created-by: [$line0]\n");
                    my ($domain, $tag, $mimetype, $apps)=($1, $2, $3, $4);
                    $mimetype=demangle_string($mimetype);
                    cxlog("domain=$domain tag=[$tag] mimetype=[$mimetype] apps=[$apps]\n");
                    $blob->{type}="created-by";
                    $blob->{domain}=$domain;
                    $blob->{tag}=$tag;
                    $blob->{mimetype}=$mimetype;
                    map { $blob->{apps}->{$_}=1 } split /;+/, $apps;
                    $self->{all_created_by}->{$mimetype}->{$domain}->{$tag}=$blob;
                    if ($self->{tag} and $self->{tag} eq $tag)
                    {
                        $self->{created_by}->{$mimetype}->{$domain}=$blob;
                    }
                }
                elsif ($line0 =~ /^\s*(#|$)/)
                {
                    cxlog("comment: [$line0]\n");
                    $blob->{type}="raw";
                }
                else
                {
                    cxlog("data: [$line0]\n");
                    my $line=$line0;
                    while (defined $line and $line =~ /\\$/)
                    {
                        $line=<$fh>;
                        last if (!defined $line);
                        chomp $line;
                        push @{$blob->{lines}}, $line;
                    }
                    $blob->{multiline}=(@{$blob->{lines}} != 1);

                    if ($line0 =~ /^\s*type=/)
                    {
                        $blob->{type}="mime";
                        $blob->{namedfields}=1;
                        $blob->{extlist}=[]; # in case 'exts=' is missing
                        # This is in the ~/.mime.types format
                        foreach my $l (@{$blob->{lines}})
                        {
                            # copy $l to not modify $blob->{lines}
                            $line = $l;
                            $line =~ s/\s*\\$//;
                            while ($line ne "")
                            {
                                my ($name, $value);
                                if ($line =~ s/^\s*([-a-zA-Z0-9_]+)\s*=\s*\"((?:[^\\\"]*|\\.)*)\"//)
                                {
                                    $name=$1;
                                    $value=unescape_string($2);
                                }
                                elsif ($line =~ s/^\s*([-a-zA-Z0-9_]+)\s*=\s*([^ =]+[^=]*[^ =]?)(?:\s+|$)//)
                                {
                                    $name=$1;
                                    $value=$2;
                                }
                                else
                                {
                                    cxlog("unable to parse '$line'\n");
                                    last;
                                }
                                $blob->{fields}->{$name}=$value;
                                if ($name eq "type")
                                {
                                    $blob->{mimetype}=$value;
                                    $self->{mimetypes}->{$value}=$blob;
                                }
                                elsif ($name eq "exts")
                                {
                                    my @extensions=split /,+/, $value;
                                    $blob->{extlist}=\@extensions;
                                }
                                elsif ($name eq "x-src")
                                {
                                    # Legacy MIME types have no notion of a
                                    # domain so just assign them to the
                                    # 'mcap' domain
                                    $blob->{created_by}->{mcap}=$value;
                                }
                            }
                        }
                    }
                    elsif ($line0 =~ /^[^ ;]*;/)
                    {
                        $blob->{type}="mailcap";
                        # This is in the ~/.mailcap or /etc/mailcap format
                        my $line0="";
                        foreach my $l (@{$blob->{lines}})
                        {
                            # copy $l to not modify $blob->{lines}
                            $line=$l;
                            $line =~ s/\\$/ /;
                            $line0.=$line;
                        }
                        my @fields=split /\s*;\s*/, $line0;
                        my $mimetype=shift @fields;
                        $self->{mimetypes}->{$mimetype}=$blob;
                        # The first field is the mimetype and the second the
                        # command but has no name. The other fields all have a
                        # name but sometimes no value.
                        $blob->{mimetype}=$mimetype;
                        $blob->{fields}->{""}=shift @fields;
                        foreach my $field (@fields)
                        {
                            if ($field =~ /^([-a-zA-Z0-9_]+)\s*=\s*\"((?:[^\\\"]*|\\.)*)\"$/)
                            {
                                my ($name, $value)=($1, $2);
                                $value=unescape_string($2);
                                $blob->{fields}->{"$name="}=$value;
                            }
                            elsif ($field =~ /^([-a-zA-Z0-9_]+)\s*=\s*(\S.*\S)$/)
                            {
                                my ($name, $value)=($1, $2);
                                $blob->{fields}->{"$name="}=$value;
                            }
                            elsif ($field =~ /^([-a-zA-Z0-9_]+)$/)
                            {
                                # This is just a boolean field so no '=' for it
                                my $name=$1;
                                $blob->{fields}->{$name}=1;
                                if ($name =~ /^x-cx/)
                                {
                                    # Legacy MIME types have no notion of a
                                    # domain so just assign them to the 'mcap'
                                    # domain
                                    $blob->{created_by}->{mcap}=$name;
                                }
                            }
                            else
                            {
                                cxlog("raw field '$field'\n");
                            }
                        }
                    }
                    elsif ($line0 !~ /;/ and @{$blob->{lines}} == 1)
                    {
                        $blob->{type}="mime";
                        # This is in the /etc/mime.types format
                        if ($line0 =~ /\s*#\s*(cx[a-z]+)\s*$/)
                        {
                            # Legacy MIME types have no notion of a domain
                            # so just assign them to the 'mcap' domain
                            $blob->{created_by}->{mcap}=$1;
                        }
                        # Remove EOL comments
                        $line0 =~ s/\s*#.*$//;
                        my @fields=split /\s+/, $line0;
                        my $mimetype=shift @fields;
                        $self->{mimetypes}->{$mimetype}=$blob;
                        $blob->{mimetype}=$mimetype;
                        $blob->{extlist}=\@fields;
                    }
                    else
                    {
                        # This is an unknown line format
                        cxlog("unable to parse:\n", join("\n", @{$blob->{lines}}),
                              "\n");
                        $blob->{type}="raw";
                        next;
                    }
                }
            }
            close($fh);
        }

        $self->{modified}=0;
        return $self;
    }

    sub isempty($)
    {
        my ($self)=@_;
        my $isempty=1;
        foreach my $blob (@{$self->{blobs}})
        {
            if ($blob->{state} ne "deleted")
            {
                $isempty=0;
                last;
            }
        }
        return $isempty;
    }

    sub save($)
    {
        my ($self)=@_;
        return 1 if (!$self->{modified});

        my $dir=cxdirname($self->{filename});
        if (!cxmkpath($dir))
        {
            cxerr("unable to create the '$dir' directory: $@\n");
            return 0;
        }

        my $fh;
        if (!open($fh, ">", $self->{filename}))
        {
            cxerr("unable to open '$self->{filename}' for writing: $!\n");
            return 0;
        }

        # Figure out what format to use for the new lines based on the first
        # non CrossOver line we find
        foreach my $blob (values %{$self->{mimetypes}})
        {
            next if ($blob->{created_by});
            next if ($self->is_crossover_mimetype($blob->{mimetype}));

            foreach my $field ("namedfields", "multiline")
            {
                if ($blob->{$field} and !defined $self->{$field})
                {
                    $self->{$field}=1;
                }
            }
            last;
        }

        cxlog("Saving '$self->{filename}'\n");
        foreach my $blob (@{$self->{blobs}})
        {
            if ($blob->{state} eq "up-to-date")
            {
                foreach my $line (@{$blob->{lines}})
                {
                    print $fh $line, "\n";
                }
            }
            elsif ($blob->{state} eq "modified")
            {
                # We have to rebuild the blob
                if ($blob->{type} eq "created-by")
                {
                    my $mangled=mangle_string($blob->{mimetype});
                    print $fh "# X-Created-By-$blob->{domain}-$blob->{tag}:$mangled=",
                              join(";", keys %{$blob->{apps}}), "\n";
                }
                elsif ($blob->{type} eq "mime")
                {
                    $blob->{namedfields}=$self->{namedfields} if (!defined $blob->{namedfields});
                    if ($blob->{namedfields})
                    {
                        $blob->{multiline}=$self->{multiline} if (!defined $blob->{multiline});
                        my $separator=($blob->{multiline} ? "\\\n" : "");
                        print $fh "type=$blob->{mimetype}";
                        print $fh " ${separator}desc=\"", $blob->{fields}->{"desc="}, "\"" if ($blob->{fields}->{"desc="});
                        print $fh " ${separator}exts=\"", join(",", @{$blob->{extlist}}),
                                  "\"\n";
                    }
                    else
                    {
                        print $fh "$blob->{mimetype} ",
                                  join(" ", @{$blob->{extlist}}),
                                  "\n";
                    }
                }
                else
                {
                    my $cmd=$blob->{fields}->{""};
                    $blob->{multiline}=$self->{multiline} if (!defined $blob->{multiline});
                    if ($blob->{multiline})
                    {
                        print $fh "$blob->{mimetype}; $cmd";
                        if ($blob->{fields}->{"nametemplate="})
                        {
                            print $fh "; \\\n    nametemplate=",
                                      $blob->{fields}->{"nametemplate="};
                        }
                        if ($blob->{fields}->{"description="})
                        {
                            print $fh "; \\\n    description=\"",
                                      escape_string($blob->{fields}->{"description="}), "\"";
                        }
                        print $fh "; \\\n    test=test -n \"\$DISPLAY\"";
                        print $fh "\n";
                    }
                    else
                    {
                        print $fh "$blob->{mimetype}; $cmd;";
                        if ($blob->{fields}->{"nametemplate="})
                        {
                            print $fh " nametemplate=",
                                      $blob->{fields}->{"nametemplate="}, ";";
                        }
                        print $fh " test=test -n \"\$DISPLAY\"\n";
                    }
                }
            }
        }
        close($fh);

        $self->{modified}=0;
        return 1;
    }

    sub delete_or_save($)
    {
        my ($self)=@_;
        if (!$self->isempty())
        {
            return $self->save();
        }
        if (-f $self->{filename})
        {
            cxlog("Deleting '$self->{filename}'\n");
            if (!unlink $self->{filename})
            {
                cxerr("unable to delete '$self->{filename}': $!\n");
                return 0;
            }
        }
        return 1;
    }
}


#####
#
# MIME database helper functions
#
#####

sub read_rwmime($)
{
    my ($self)=@_;
    if (!$self->{rwmime})
    {
        cxlog("Reading '$self->{mime}'\n");
        $self->{rwmime}=CXRWBlob->new($self->{tag}, $self->{mime}, "mime", 0);
    }
}

sub process_mime_file($$)
{
    my ($self, $rwfile)=@_;

    foreach my $blob (values %{$rwfile->{mimetypes}})
    {
        next if ($blob->{created_by});
        next if ($rwfile->is_crossover_mimetype($blob->{mimetype}));
        # For each MIME type, the extension list defined in /etc/mime.types
        # is merged with the one defined in ~/.mime.types, so do the same here.
        $self->mdb_add_mime($blob->{mimetype}, $blob->{extlist});
    }
}

# In this function we want to get a list of the *native Mailcap* MIME types
# and their associated extensions into the MIME database. We don't want to
# include any MIME type created by CrossOver in this list. The actual filtering
# is done by process_mime_file().
sub read_mime_db($)
{
    my ($self)=@_;
    return if ($self->{read_mime_db});
    $self->{read_mime_db}=1;

    $self->read_rwmime();
    $self->process_mime_file($self->{rwmime});

    if ($self->{global_mime} ne $self->{mime})
    {
        cxlog("Reading '$self->{global_mime}'\n");
        my $gmime=CXRWBlob->new($self->{tag}, $self->{global_mime}, "mime", 0);
        $self->process_mime_file($gmime);
    }
}


#####
#
# MIME type creation and deletion
#
#####

sub create_mime($$$$$$)
{
    my ($self, $domain, $massoc, $mime, $mimetype, $extensions)=@_;
    my $rwmime=$self->{rwmime};

    my $cxmime=$rwmime->add("mime", $mimetype, $domain);
    if (!$cxmime->{created})
    {
        cxlog("Creating '$mimetype'\n");

        CXAssoc::setup_from_best_eassoc($mime);
        my $description=$mime->{description} || $mimetype;
        $description.=" (CXMimeMcap)" if ($ENV{CX_TAGALL});

        # Recreate the MIME type from scratch...
        # Note:
        # - We should probably convert the description to the system
        #   encoding. But then I have not found a single application that
        #   uses it so there's no telling what is really allowed.
        $cxmime->{fields}={ "desc=" => $description };
        $cxmime->{extlist}=$extensions;
        # FIXME: It might be possible to use the icon, to be checked
        delete $cxmime->{lines};
        delete $cxmime->{created_by};

        $cxmime->{created}=1;
        $rwmime->modified($cxmime);
    }
    else
    {
        cxlog("Tagging  '$mimetype'\n");
    }

    my $created_by=$rwmime->add("created-by", $mimetype, $domain);
    if (!exists $created_by->{apps}->{$massoc->{id}})
    {
        $created_by->{apps}->{$massoc->{id}}=1;
        $rwmime->modified($created_by);
    }

    $self->{modified_mime}=1;
    return 1;
}

sub query_mime($$$$$)
{
    my ($self, $domain, $massoc, $mimetype, $extensions)=@_;
    my $rwmime=$self->{rwmime};

    my $created_by=$rwmime->get_created_by($domain, $mimetype);
    return 0 if (!$created_by->{apps}->{$massoc->{id}});

    my $cxmime=$rwmime->get($mimetype);
    return 0 if (!$cxmime);
    if (!$cxmime->{extensions})
    {
        map { $cxmime->{extensions}->{$_}=1 } @{$cxmime->{extlist}};
    }
    foreach my $ext (@$extensions)
    {
        return 0 if (!$cxmime->{extensions}->{$ext});
    }

    return 1;
}

sub get_mime_files($$$$$)
{
    #my ($self, $domain, $massoc, $mimetype, $extensions)=@_;
    # The mime.types file is not specific to this bottle
    # and thus must not be packaged with it.
    return [];
}

sub untag_mime($$$$)
{
    my ($self, $domain, $massoc, $mimetype)=@_;
    my $rwmime=$self->{rwmime};

    my $created_by=$rwmime->get_created_by($domain, $mimetype);
    return 1 if (!$created_by);

    if ($created_by->{apps}->{$massoc->{id}})
    {
        delete $created_by->{apps}->{$massoc->{id}};
        $rwmime->modified($created_by);
    }

    if (!%{$created_by->{apps}})
    {
        if (!$rwmime->remove_created_by($created_by))
        {
            # No one is using this MIME type anymore
            my $cxmime=$rwmime->get($mimetype);
            if ($cxmime)
            {
                $rwmime->remove($cxmime);
            }
        }
    }

    return 1;
}



#####
#
# Main
#
#####

my $cxmimemcap;
sub get($$$$)
{
    my ($class, $domain, $cxoptions, $gui_info)=@_;
    if (!$cxmimemcap)
    {
        $cxmimemcap={
            mime           => "$cxoptions->{destdir}$gui_info->{mailcap_preferred_mime}",
            global_mime    => $gui_info->{mailcap_global_mime},
            domains        => []
        };
        bless $cxmimemcap, $class;
        $cxmimemcap->init_mime_handler($cxoptions);
    }
    if (!grep /^$domain$/, @{$cxmimemcap->{domains}})
    {
        push @{$cxmimemcap->{domains}}, $domain;
    }
    return $cxmimemcap;
}

sub removeall($$)
{
    my ($self, $domain, $pattern)=@_;
    $self->read_rwmime();
    if ($pattern eq "legacy")
    {
        foreach my $blob (@{$self->{rwmime}->{blobs}})
        {
            if (($blob->{created_by} || "") eq "cxoffice")
            {
                cxlog("Removing '$blob->{mimetype}' from mime.types\n");
                $self->{rwmime}->remove($blob);
            }
        }
    }
    else
    {
        my $all_created_by=$self->{rwmime}->{all_created_by};
        foreach my $mimetype (keys %$all_created_by)
        {
            foreach my $created_by (values %{$all_created_by->{$mimetype}->{$domain}})
            {
                if ($created_by->{tag} =~ /^$pattern/)
                {
                    if (!$self->{rwmime}->remove_created_by($created_by))
                    {
                        # No one is using this MIME type anymore
                        my $cxmime=$self->{rwmime}->get($mimetype);
                        if ($cxmime)
                        {
                            cxlog("Deleting $mimetype\n");
                            $self->{rwmime}->remove($cxmime);
                        }
                        else
                        {
                            cxlog("Untagged $mimetype\n");
                        }
                    }
                }
            }
        }
    }
}

sub finalize($)
{
    my ($self)=@_;
    return $self->{rwmime}->delete_or_save() if (defined $self->{rwmime});
    return 0;
}

return 1;