View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.tika.mime;
18  
19  import java.util.ArrayList;
20  
21  /**
22   * Internet media type.
23   */
24  public final class MimeType implements Comparable<MimeType> {
25  
26      /**
27       * Checks that the given string is a valid Internet media type name
28       * based on rules from RFC 2054 section 5.3. For validation purposes the
29       * rules can be simplified to the following:
30       * <pre>
31       * name := token "/" token
32       * token := 1*&lt;any (US-ASCII) CHAR except SPACE, CTLs, or tspecials&gt;
33       * tspecials :=  "(" / ")" / "&lt;" / "&gt;" / "@" / "," / ";" / ":" /
34       *               "\" / <"> / "/" / "[" / "]" / "?" / "="
35       * </pre>
36       *
37       * @param name name string
38       * @return <code>true</code> if the string is a valid media type name,
39       *         <code>false</code> otherwise
40       */
41      public static boolean isValid(String name) {
42          if (name == null) {
43              throw new IllegalArgumentException("Name is missing");
44          }
45  
46          boolean slash = false;
47          for (int i = 0; i < name.length(); i++) {
48              char ch = name.charAt(i);
49              if (ch <= ' ' || ch >= 127 || ch == '(' || ch == ')' ||
50                      ch == '<' || ch == '>' || ch == '@' || ch == ',' ||
51                      ch == ';' || ch == ':' || ch == '\\' || ch == '"' ||
52                      ch == '[' || ch == ']' || ch == '?' || ch == '=') {
53                  return false;
54              } else if (ch == '/') {
55                  if (slash || i == 0 || i + 1 == name.length()) {
56                      return false;
57                  }
58                  slash = true;
59              }
60          }
61          return slash;
62      }
63  
64      /**
65       * The media type registry that contains this type.
66       */
67      private final MimeTypes registry;
68  
69      /**
70       * Lower case name of this media type.
71       */
72      private final String name;
73  
74      /**
75       * Description of this media type.
76       */
77      private String description = "";
78  
79      /**
80       * The parent type of this media type, or <code>null</code> if this
81       * is a top-level type.
82       */
83      private MimeType superType = null;
84  
85      /** The magics associated to this Mime-Type */
86      private final ArrayList<Magic> magics = new ArrayList<Magic>();
87  
88      /** The root-XML associated to this Mime-Type */
89      private final ArrayList<RootXML> rootXML = new ArrayList<RootXML>();
90  
91      /** The minimum length of data to provides for magic analyzis */
92      private int minLength = 0;
93  
94      /**
95       * Creates a media type with the give name and containing media type
96       * registry. The name is expected to be valid and normalized to lower
97       * case. This constructor should only be called by
98       * {@link MimeTypes#forName(String)} to keep the media type registry
99       * up to date.
100      *
101      * @param registry the media type registry that contains this type
102      * @param name media type name
103      */
104     MimeType(MimeTypes registry, String name) {
105         if (registry == null) {
106             throw new IllegalArgumentException("Registry is missing");
107         }
108         if (!MimeType.isValid(name) || !name.equals(name.toLowerCase())) {
109             throw new IllegalArgumentException("Media type name is invalid");
110         }
111         this.registry = registry;
112         this.name = name;
113     }
114 
115     /**
116      * Returns the name of this media type.
117      *
118      * @return media type name (lower case)
119      */
120     public String getName() {
121         return name;
122     }
123 
124     /**
125      * Returns the parent of this media type.
126      *
127      * @return parent media type, or <code>null</code>
128      */
129     public MimeType getSuperType() {
130         return superType;
131     }
132 
133     public void setSuperType(MimeType type) throws MimeTypeException {
134         if (type == null) {
135             throw new IllegalArgumentException("MimeType is missing");
136         }
137         if (type.registry != registry) {
138             throw new IllegalArgumentException("MimeType is from a different registry");
139         }
140         if (this.isDescendantOf(type)) {
141             // ignore, already a descendant of the given type
142         } else if (this == type) {
143             throw new MimeTypeException(
144                     "Media type can not inherit itself: " + type);
145         } else if (type.isDescendantOf(this)) {
146             throw new MimeTypeException(
147                     "Media type can not inherit its descendant: " + type);
148         } else if (superType == null) {
149             superType = type;
150         } else if (type.isDescendantOf(superType)) {
151             superType = type;
152         } else {
153             throw new MimeTypeException(
154                     "Conflicting media type inheritance: " + type);
155         }
156     }
157 
158     public boolean isDescendantOf(MimeType type) {
159         if (type == null) {
160             throw new IllegalArgumentException("MimeType is missing");
161         }
162         synchronized (registry) {
163             for (MimeType t = superType; t != null; t = t.superType) {
164                 if (t == type) {
165                     return true;
166                 }
167             }
168             return false;
169         }
170     }
171 
172     /**
173      * Returns the description of this media type.
174      *
175      * @return media type description
176      */
177     public String getDescription() {
178         return description;
179     }
180 
181     /**
182      * Set the description of this media type.
183      *
184      * @param description media type description
185      */
186     public void setDescription(String description) {
187         if (description == null) {
188             throw new IllegalArgumentException("Description is missing");
189         }
190         this.description = description;
191     }
192 
193     /**
194      * Adds an alias name for this media type.
195      *
196      * @param alias media type alias (case insensitive)
197      * @throws MimeTypeException if the alias is invalid or
198      *                           already registered for another media type
199      */
200     public void addAlias(String alias) throws MimeTypeException {
201         if (isValid(alias)) {
202             alias = alias.toLowerCase();
203             if (!name.equals(alias)) {
204                 registry.addAlias(this, alias);
205             }
206         } else {
207             throw new MimeTypeException("Invalid media type alias: " + alias);
208         }
209     }
210 
211     /**
212      * Add some rootXML info to this mime-type
213      *
214      * @param namespaceURI
215      * @param localName
216      */
217     void addRootXML(String namespaceURI, String localName) {
218         rootXML.add(new RootXML(this, namespaceURI, localName));
219     }
220 
221     boolean matchesXML(String namespaceURI, String localName) {
222         for (RootXML xml : rootXML) {
223             if (xml.matches(namespaceURI, localName)) {
224                 return true;
225             }
226         }
227         return false;
228     }
229 
230     boolean hasRootXML() {
231         return (rootXML.size() > 0);
232     }
233 
234     RootXML[] getRootXMLs() {
235         return rootXML.toArray(new RootXML[rootXML.size()]);
236     }
237 
238     Magic[] getMagics() {
239         return magics.toArray(new Magic[magics.size()]);
240     }
241 
242     void addMagic(Magic magic) {
243         if (magic == null) {
244             return;
245         }
246         magics.add(magic);
247     }
248 
249     int getMinLength() {
250         return minLength;
251     }
252 
253     public boolean hasMagic() {
254         return (magics.size() > 0);
255     }
256 
257     public boolean matchesMagic(byte[] data) {
258         for (int i = 0; i < magics.size(); i++) {
259             Magic magic = magics.get(i);
260             if (magic.eval(data)) {
261                 return true;
262             }
263         }
264         return false;
265     }
266 
267     public boolean matches(byte[] data) {
268         return matchesMagic(data);
269     }
270 
271     /**
272      * Defines a RootXML description. RootXML is made of a localName and/or a
273      * namespaceURI.
274      */
275     class RootXML {
276 
277         private MimeType type = null;
278 
279         private String namespaceURI = null;
280 
281         private String localName = null;
282 
283         RootXML(MimeType type, String namespaceURI, String localName) {
284             if (isEmpty(namespaceURI) && isEmpty(localName)) {
285                 throw new IllegalArgumentException(
286                         "Both namespaceURI and localName cannot be empty");
287             }
288             this.type = type;
289             this.namespaceURI = namespaceURI;
290             this.localName = localName;
291         }
292 
293         boolean matches(String namespaceURI, String localName) {
294             //Compare namespaces
295             if (!isEmpty(this.namespaceURI)) {
296                 if (!this.namespaceURI.equals(namespaceURI)) {
297                     return false;
298                 }
299             }
300             else{
301                 // else if it was empty then check to see if the provided namespaceURI
302                 // is empty. If it is not, then these two aren't equal and return false
303                 if(!isEmpty(namespaceURI)){
304                     return false;
305                 }
306             }
307 
308             //Compare root element's local name
309             if (!isEmpty(this.localName)) {
310                 if (!this.localName.equals(localName)) {
311                     return false;
312                 }
313             }
314             else{
315                 // else if it was empty then check to see if the provided localName
316                 // is empty. If it is not, then these two aren't equal and return false 
317                 if(!isEmpty(localName)){
318                     return false;
319                 }
320             }
321             return true;
322         }
323 
324         /**
325          * Checks if a string is null or empty.
326          */
327         private boolean isEmpty(String str) {
328             return (str == null) || (str.equals(""));
329         }
330 
331         MimeType getType() {
332             return type;
333         }
334 
335         String getNameSpaceURI() {
336             return namespaceURI;
337         }
338 
339         String getLocalName() {
340             return localName;
341         }
342 
343         public String toString() {
344             return type + ", " + namespaceURI + ", " + localName;
345         }
346     }
347 
348     //----------------------------------------------------------< Comparable >
349 
350     public int compareTo(MimeType type) {
351         if (type == null) {
352             throw new IllegalArgumentException("MimeType is missing");
353         }
354         if (type == this) {
355             return 0;
356         } else if (this.isDescendantOf(type)) {
357             return 1;
358         } else if (type.isDescendantOf(this)) {
359             return -1;
360         } else if (superType != null) {
361             return superType.compareTo(type);
362         } else if (type.superType != null) {
363             return compareTo(type.superType);
364         } else {
365             return name.compareTo(type.name);
366         }
367     }
368 
369     //--------------------------------------------------------------< Object >
370 
371     /**
372      * Returns the name of this media type.
373      *
374      * @return media type name
375      */
376     public String toString() {
377         return name;
378     }
379 
380 }