blob: 11d20d4d6c807f0c28f80557d23597440fa86c5c [file] [log] [blame]
Tobias Thierer878c77b2019-08-18 15:19:45 +01001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content.type;
18
Tobias Thierer8edd8372019-10-03 13:43:21 +010019import libcore.content.type.MimeMap;
Tobias Thierer878c77b2019-08-18 15:19:45 +010020
21import java.io.BufferedReader;
22import java.io.IOException;
Tobias Thiereradeea592019-09-27 20:27:29 +010023import java.io.InputStream;
Tobias Thierer878c77b2019-08-18 15:19:45 +010024import java.io.InputStreamReader;
Tobias Thierer32434912019-09-27 19:22:39 +010025import java.util.ArrayList;
Tobias Thierer878c77b2019-08-18 15:19:45 +010026import java.util.List;
Tobias Thiereradeea592019-09-27 20:27:29 +010027import java.util.Objects;
28import java.util.function.Function;
Tobias Thierer878c77b2019-08-18 15:19:45 +010029
30/**
Tobias Thiereraf0cef92019-09-27 17:08:49 +010031 * Creates the framework default {@link MimeMap}, a bidirectional mapping
32 * between MIME types and file extensions.
Tobias Thierer878c77b2019-08-18 15:19:45 +010033 *
34 * This default mapping is loaded from data files that start with some mappings
35 * recognized by IANA plus some custom extensions and overrides.
36 *
37 * @hide
38 */
Tobias Thiereraf0cef92019-09-27 17:08:49 +010039public class DefaultMimeMapFactory {
40
41 private DefaultMimeMapFactory() {
42 }
Tobias Thierer878c77b2019-08-18 15:19:45 +010043
44 /**
Tobias Thiereraf0cef92019-09-27 17:08:49 +010045 * Creates and returns a new {@link MimeMap} instance that implements.
Tobias Thierer878c77b2019-08-18 15:19:45 +010046 * Android's default mapping between MIME types and extensions.
47 */
Tobias Thiereraf0cef92019-09-27 17:08:49 +010048 public static MimeMap create() {
Tobias Thiereradeea592019-09-27 20:27:29 +010049 Class c = DefaultMimeMapFactory.class;
50 // The resources are placed into the res/ path by the "mimemap-res.jar" genrule.
51 return create(resourceName -> c.getResourceAsStream("/res/" + resourceName));
Tobias Thierer878c77b2019-08-18 15:19:45 +010052 }
53
Tobias Thiereradeea592019-09-27 20:27:29 +010054 /**
55 * Creates a {@link MimeMap} instance whose resources are loaded from the
56 * InputStreams looked up in {@code resourceSupplier}.
57 *
58 * @hide
59 */
60 public static MimeMap create(Function<String, InputStream> resourceSupplier) {
61 MimeMap.Builder builder = MimeMap.builder();
Tobias Thierer8e1c1f42019-10-08 16:27:35 +010062 // The files loaded here must be in minimized format with lines of the
63 // form "mime/type ext1 ext2 ext3", i.e. no comments, no empty lines, no
64 // leading/trailing whitespace and with a single space between entries on
65 // each line. See http://b/142267887
66 //
67 // Note: the order here matters - later entries can overwrite earlier ones
68 // (except that vendor.mime.types entries are prefixed with '?' which makes
69 // them never overwrite).
70 parseTypes(builder, resourceSupplier, "debian.mime.types");
71 parseTypes(builder, resourceSupplier, "android.mime.types");
72 parseTypes(builder, resourceSupplier, "vendor.mime.types");
Tobias Thiereradeea592019-09-27 20:27:29 +010073 return builder.build();
74 }
75
Tobias Thierer8e1c1f42019-10-08 16:27:35 +010076 private static void parseTypes(MimeMap.Builder builder,
Tobias Thiereradeea592019-09-27 20:27:29 +010077 Function<String, InputStream> resourceSupplier, String resourceName) {
78 try (InputStream inputStream = Objects.requireNonNull(resourceSupplier.apply(resourceName));
79 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
Tobias Thierer878c77b2019-08-18 15:19:45 +010080 String line;
Tobias Thierer8e1c1f42019-10-08 16:27:35 +010081 List<String> specs = new ArrayList<>(10); // re-use for each line
Tobias Thiereradeea592019-09-27 20:27:29 +010082 while ((line = reader.readLine()) != null) {
Tobias Thierer8e1c1f42019-10-08 16:27:35 +010083 specs.clear();
84 // Lines are of the form "mimeSpec extSpec extSpec[...]" with a single space
85 // separating them and no leading/trailing spaces and no empty lines.
86 int startIdx = 0;
87 do {
88 int endIdx = line.indexOf(' ', startIdx);
89 if (endIdx < 0) {
90 endIdx = line.length();
91 }
92 String spec = line.substring(startIdx, endIdx);
93 if (spec.isEmpty()) {
94 throw new IllegalArgumentException("Malformed line: " + line);
95 }
96 specs.add(spec);
97 startIdx = endIdx + 1; // skip over the space
98 } while (startIdx < line.length());
Tobias Thiererfd9657d2019-08-20 15:23:34 +010099 builder.put(specs.get(0), specs.subList(1, specs.size()));
Tobias Thierer878c77b2019-08-18 15:19:45 +0100100 }
101 } catch (IOException | RuntimeException e) {
Tobias Thiereradeea592019-09-27 20:27:29 +0100102 throw new RuntimeException("Failed to parse " + resourceName, e);
Tobias Thierer878c77b2019-08-18 15:19:45 +0100103 }
104 }
105
Tobias Thierer878c77b2019-08-18 15:19:45 +0100106}