1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178

base / nix / mime_util_xdg_unittest.cc [blame]

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/nix/mime_util_xdg.h"

#include <map>
#include <string>
#include <vector>

#include "base/base64.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::nix {
namespace {

// Test mime.cache files are generated using a process such as:
// mkdir -p /tmp/mimetest/packages
// cat <<EOF >> /tmp/mimetest/packages/application-x-foobar.xml
// <?xml version="1.0" encoding="UTF-8"?>
// <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
//   <mime-type type="x/no-dot"><glob pattern="~"/></mime-type>
//   <mime-type type="application/pdf"><glob pattern="*.pdf"/></mime-type>
//   <mime-type type="text/plain"><glob pattern="*.txt"/></mime-type>
//   <mime-type type="text/plain"><glob pattern="*.doc"/></mime-type>
//   <mime-type type="x/ignore"><glob pattern="*.foo" weight="60"/></mime-type>
//   <mime-type type="x/foo"><glob pattern="*.foo" weight="80"/></mime-type>
//   <mime-type type="text/plain"><glob pattern="*.foo"/></mime-type>
//   <mime-type type="x/smile"><glob pattern="*.🙂🤩"/></mime-type>
// </mime-info>
// EOF
// update-mime-database /tmp/mimetest
// base64 -w72 /tmp/mimetest/mime.cache
// See https://wiki.archlinux.org/title/XDG_MIME_Applications

constexpr char kTestMimeCacheB64[] =
    "AAEAAgAAAHQAAAB4AAAAfAAAAIwAAAHMAAAB0AAAAdwAAAHgAAAB5AAAAehhcHBsaWNhdGlv"
    "bi9wZGYAeC9zbWlsZQB4L2lnbm9yZQAAAAB0ZXh0L3BsYWluAAB4L2ZvbwAAAH4AAAB4L25v"
    "LWRvdAAAAAAAAAAAAAAAAAAAAAEAAABkAAAAaAAAADIAAAAFAAAAlAAAAGMAAAABAAAA0AAA"
    "AGYAAAABAAAA3AAAAG8AAAABAAAA6AAAAHQAAAABAAAA9AAB+SkAAAABAAABAAAAAG8AAAAB"
    "AAABDAAAAGQAAAABAAABGAAAAG8AAAABAAABJAAAAHgAAAABAAABMAAB9kIAAAABAAABPAAA"
    "AGQAAAABAAABSAAAAHAAAAABAAABVAAAAGYAAAABAAABYAAAAHQAAAABAAABbAAAAC4AAAAB"
    "AAABeAAAAC4AAAABAAABhAAAAC4AAAABAAABkAAAAC4AAAADAAABnAAAAC4AAAABAAABwAAA"
    "AAAAAAA8AAAAMgAAAAAAAABQAAAAMgAAAAAAAAAsAAAAMgAAAAAAAABEAAAAPAAAAAAAAABc"
    "AAAAUAAAAAAAAABQAAAAMgAAAAAAAABQAAAAMgAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAA"
    "AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

class ParseMimeTypesTest : public ::testing::Test {
 public:
  ParseMimeTypesTest() {
    CHECK(temp_dir_.CreateUniqueTempDir());
    mime_types_path_ = temp_dir_.GetPath().Append("mime.types");
  }
  ParseMimeTypesTest(const ParseMimeTypesTest&) = delete;
  ParseMimeTypesTest& operator=(const ParseMimeTypesTest&) = delete;

  ~ParseMimeTypesTest() override = default;

  // Ensures that parsing fails when mime.cache file is modified such that
  // `buf[pos] = c`.
  void InvalidIf(std::vector<uint8_t>& buf, size_t pos, uint8_t c) {
    ASSERT_LT(pos, buf.size());
    uint8_t old_c = buf[pos];
    buf[pos] = c;
    ASSERT_TRUE(base::WriteFile(TempFile(), buf));
    MimeTypeMap map;
    EXPECT_FALSE(ParseMimeTypes(TempFile(), map));
    buf[pos] = old_c;
  }

  const FilePath& TempFile() const { return mime_types_path_; }

 private:
  ScopedTempDir temp_dir_;
  FilePath mime_types_path_;
};

}  // namespace

bool operator==(const WeightedMime& lhs, const WeightedMime& rhs) {
  return lhs.mime_type == rhs.mime_type && lhs.weight == rhs.weight;
}

TEST_F(ParseMimeTypesTest, NonExistentFileFails) {
  MimeTypeMap map;
  EXPECT_FALSE(ParseMimeTypes(FilePath("/invalid/filepath/foo"), map));
}

TEST_F(ParseMimeTypesTest, ValidResult) {
  MimeTypeMap map;
  auto buf = Base64Decode(kTestMimeCacheB64);
  ASSERT_TRUE(buf.has_value());
  ASSERT_TRUE(WriteFile(TempFile(), *buf));
  EXPECT_TRUE(ParseMimeTypes(TempFile(), map));
  const MimeTypeMap kExpected = {
      {"pdf", {"application/pdf", 50}}, {"txt", {"text/plain", 50}},
      {"doc", {"text/plain", 50}},      {"foo", {"x/foo", 80}},
      {"🙂🤩", {"x/smile", 50}},
  };
  EXPECT_EQ(map, kExpected);
}

TEST_F(ParseMimeTypesTest, Empty) {
  MimeTypeMap map;
  ASSERT_TRUE(WriteFile(TempFile(), ""));
  EXPECT_FALSE(ParseMimeTypes(TempFile(), map));
}

// xxd /tmp/mimetest/mime.cache
// 00000000: 0001 0002 0000 0074 0000 0078 0000 007c  .......t...x...|
// 00000010: 0000 008c 0000 01cc 0000 01d0 0000 01dc  ................
// 00000020: 0000 01e0 0000 01e4 0000 01e8 6170 706c  ............appl
// 00000030: 6963 6174 696f 6e2f 7064 6600 782f 736d  ication/pdf.x/sm
// 00000040: 696c 6500 782f 6967 6e6f 7265 0000 0000  ile.x/ignore....
// 00000050: 7465 7874 2f70 6c61 696e 0000 782f 666f  text/plain..x/fo
// 00000060: 6f00 0000 7e00 0000 782f 6e6f 2d64 6f74  o...~...x/no-dot
// 00000070: 0000 0000 0000 0000 0000 0000 0000 0001  ................
// 00000080: 0000 0064 0000 0068 0000 0032 0000 0005  ...d...h...2....
// 00000090: 0000 0094 0000 0063 0000 0001 0000 00d0  .......c........
// 000000a0: 0000 0066 0000 0001 0000 00dc 0000 006f  ...f...........o
// 000000b0: 0000 0001 0000 00e8 0000 0074 0000 0001  ...........t....
// 000000c0: 0000 00f4 0001 f929 0000 0001 0000 0100  .......)........
// 000000d0: 0000 006f 0000 0001 0000 010c 0000 0064  ...o...........d
// 000000e0: 0000 0001 0000 0118 0000 006f 0000 0001  ...........o....
// 000000f0: 0000 0124 0000 0078 0000 0001 0000 0130  ...$...x.......0
// 00000100: 0001 f642 0000 0001 0000 013c 0000 0064  ...B.......<...d
// 00000110: 0000 0001 0000 0148 0000 0070 0000 0001  .......H...p....
// 00000120: 0000 0154 0000 0066 0000 0001 0000 0160  ...T...f.......`
// 00000130: 0000 0074 0000 0001 0000 016c 0000 002e  ...t.......l....
// 00000140: 0000 0001 0000 0178 0000 002e 0000 0001  .......x........
// 00000150: 0000 0184 0000 002e 0000 0001 0000 0190  ................
// 00000160: 0000 002e 0000 0003 0000 019c 0000 002e  ................
// 00000170: 0000 0001 0000 01c0 0000 0000 0000 003c  ...............<
// 00000180: 0000 0032 0000 0000 0000 0050 0000 0032  ...2.......P...2
// 00000190: 0000 0000 0000 002c 0000 0032 0000 0000  .......,...2....
// 000001a0: 0000 0044 0000 003c 0000 0000 0000 005c  ...D...<.......\
// 000001b0: 0000 0050 0000 0000 0000 0050 0000 0032  ...P.......P...2
// 000001c0: 0000 0000 0000 0050 0000 0032 0000 0000  .......P...2....
// 000001d0: 0000 0000 0000 0000 0000 01dc 0000 0000  ................
// 000001e0: 0000 0000 0000 0000 0000 0006 0000 0000  ................
// 000001f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
// 00000200: 0000 0000
TEST_F(ParseMimeTypesTest, Invalid) {
  auto buf = Base64Decode(kTestMimeCacheB64);
  ASSERT_TRUE(buf.has_value());
  // ALIAS_LIST_OFFSET is uint32 at byte 4 = 0x74.
  // Alias list offset inside header.
  InvalidIf(*buf, 7, 0xa);
  // Alias list offset larger than file size.
  InvalidIf(*buf, 6, 0xff);
  // Not null beore alias list.
  InvalidIf(*buf, 0x74 - 1, 'X');
  // Misaligned offset for REVERSE_SUFFIX_TREE_OFFSET.
  InvalidIf(*buf, 0x13, 0x7a);
  // N_ROOTS > kMaxUnicode (0x10ffff).
  InvalidIf(*buf, 0x8d, 0x20);
  InvalidIf(*buf, 0xd5, 0x20);
  // Node C > kMaxUnicode (0x10ffff).
  InvalidIf(*buf, 0x95, 0x20);
  // Node N_CHILDREN > kMaxUnicode (0x10ffff).
  InvalidIf(*buf, 0x99, 0x20);
  // Node FIRST_CHILD_OFFSET below tree offset.
  InvalidIf(*buf, 0x9f, 0x10);
  InvalidIf(*buf, 0xdb, 0x20);
  // Node FIRST_CHILD_OFFSET beyond file size.
  InvalidIf(*buf, 0x9e, 0x20);
  InvalidIf(*buf, 0xda, 0x20);
  // Mime type offset below header.
  InvalidIf(*buf, 0x18b, 0x10);
  // Mime type offset above alias list.
  InvalidIf(*buf, 0x18b, 0x74);
}

}  // namespace base::nix