1 /*
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 
17 package android.content.type;
18 
19 import libcore.content.type.MimeMap;
20 
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.function.Function;
29 
30 /**
31  * Creates the framework default {@link MimeMap}, a bidirectional mapping
32  * between MIME types and file extensions.
33  *
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  */
39 public class DefaultMimeMapFactory {
40 
DefaultMimeMapFactory()41     private DefaultMimeMapFactory() {
42     }
43 
44     /**
45      * Creates and returns a new {@link MimeMap} instance that implements.
46      * Android's default mapping between MIME types and extensions.
47      */
create()48     public static MimeMap create() {
49         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));
52     }
53 
54     /**
55      * Creates a {@link MimeMap} instance whose resources are loaded from the
56      * InputStreams looked up in {@code resourceSupplier}.
57      *
58      * @hide
59      */
create(Function<String, InputStream> resourceSupplier)60     public static MimeMap create(Function<String, InputStream> resourceSupplier) {
61         MimeMap.Builder builder = MimeMap.builder();
62         // 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");
73         return builder.build();
74     }
75 
parseTypes(MimeMap.Builder builder, Function<String, InputStream> resourceSupplier, String resourceName)76     private static void parseTypes(MimeMap.Builder builder,
77             Function<String, InputStream> resourceSupplier, String resourceName) {
78         try (InputStream inputStream = Objects.requireNonNull(resourceSupplier.apply(resourceName));
79              BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
80             String line;
81             List<String> specs = new ArrayList<>(10); // re-use for each line
82             while ((line = reader.readLine()) != null) {
83                 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());
99                 builder.put(specs.get(0), specs.subList(1, specs.size()));
100             }
101         } catch (IOException | RuntimeException e) {
102             throw new RuntimeException("Failed to parse " + resourceName, e);
103         }
104     }
105 
106 }
107