001 /*
002 Copyright (c) 1996-2011, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.svrCore.MIME;
030
031 import java.io.DataInputStream;
032 import java.io.File;
033 import java.io.FileInputStream;
034 import java.io.IOException;
035 import java.io.InputStream;
036 import java.io.InvalidObjectException;
037 import java.util.Arrays;
038 import java.util.Collections;
039 import java.util.HashSet;
040 import java.util.Hashtable;
041 import java.util.Set;
042
043 import org.hd.d.pg2k.svrCore.FileTools;
044
045 /**Routines to establish/check exhibit MIME types.
046 * This is hard-wired into the Gallery for now; we don't allow dynamic
047 * loading of new types and handlers...
048 * <p>
049 * This is a rewrite and extension of the old (pre-PG2K) PG Attributes class.
050 */
051 public final class ExhibitMIME
052 {
053 /**Package for default media handlers. */
054 public static final String DEFAULT_HANDLER_PACKAGE =
055 "org.hd.d.pg2k.svrCore.mediahandler";
056
057 /**JavaBean outlining file details of one Gallery exhibit item type.
058 * This object is immutable.
059 * <p>
060 * Not designed to be persisted, so not Serializable.
061 * <p>
062 * Note that this has no public no-arg constructor because
063 * these objects are all created statically and we create
064 * all the instances that should be created.
065 * <p>
066 * Note that this class is based on the assumption that we
067 * recognise exactly one filename suffix for each type of
068 * file archived in the Gallery, and when generating something
069 * of that type (usually) use one, different, suffix. Both
070 * suffixes are chose to be generally acceptable to most tools
071 * such as browsers.
072 * <p>
073 * These suffixes presented are intern()ed so that the number
074 * of copies of them kicking around can be reduced. Don't go
075 * casually making up lots of random file-types...
076 */
077 public static final class ExhibitTypeParameters implements Comparable<ExhibitTypeParameters>
078 {
079 /**Make a new MIME type descriptor.
080 * Private constructor and object not (de)serialisable
081 * so as to ensure that only we can make instances here at run-time.
082 *
083 * @param _handlerClassName the fully-qualified class name
084 * of the handler for this MIME type; if null a default
085 * of DEFAULT_HANDLER_PACKAGE package with the class name
086 * the same as the primary (all-lower-case) suffix
087 * is used instead
088 */
089 private ExhibitTypeParameters(final int _type,
090 final String _suffixForInputFile,
091 final String _mimeType,
092 final String _handlerClassName,
093 final String _magic,
094 final String _description)
095 {
096 this(_type, _suffixForInputFile, null, _mimeType, _handlerClassName, _magic, null, _description);
097 }
098
099 /**Make a new MIME type descriptor.
100 * Private constructor and object not (de)serialisable
101 * so as to ensure that only we can make instances here
102 * and at run-time.
103 *
104 * @param _allSuffixes null or a set of (dotless) extensions
105 * (possibly including the primary one)
106 * by which we may recognise input files of this type
107 * @param _allMagics null or a set of magic numbers/strings
108 * (possibly including the primary one if not "")
109 * by which we may recognise input files of this type
110 *
111 * @param _handlerClassName the fully-qualified class name
112 * of the handler for this MIME type; if null a default
113 * of DEFAULT_HANDLER_PACKAGE package with the class name
114 * the same as the primary (all-lower-case) suffix
115 * is used instead
116 */
117 private ExhibitTypeParameters(final int _type,
118 final String _suffixForInputFile,
119 final String _allSuffixes[],
120 final String _mimeType,
121 String _handlerClassName,
122 final String _magic,
123 final String _allMagics[],
124 final String _description)
125 {
126 if((_type < ET__min) || (_type > ET__max) ||
127 (_suffixForInputFile == null) ||
128 (_suffixForInputFile.length() < 1) ||
129 (_suffixForInputFile.startsWith(".")) ||
130 (_mimeType == null) ||
131 (_mimeType.length() < 3) || // Must be at least "a/b"...
132 (_mimeType.indexOf('/') < 1) || // Must be at least "a/b"...
133 (!_mimeType.equals(_mimeType.toLowerCase())) || // Must be lower-case.
134 (_magic == null) ||
135 ("".equals(_handlerClassName)) || // Must not be empty string.
136 (_description == null))
137 {
138 throw new IllegalArgumentException();
139 }
140 type = _type;
141 suffixForInputFile = _suffixForInputFile.intern();
142 dotSuffixForInputFile = ("." + _suffixForInputFile).intern();
143 mimeType = _mimeType;
144 magic = _magic;
145 description = _description;
146
147 // Set up the suffixes list...
148 if(_allSuffixes == null)
149 {
150 allSuffixesForFile = Collections.singleton(suffixForInputFile);
151 }
152 else
153 {
154 final Set<String> s = new HashSet<String>(2 + _allSuffixes.length*2);
155 s.add(suffixForInputFile); // Ensure that the primary value is present.
156 for(int i = _allSuffixes.length; --i >= 0; )
157 {
158 final String t = _allSuffixes[i];
159 if((t == null) || (t.length() < 1) || (t.startsWith(".")))
160 { throw new IllegalArgumentException(); }
161 s.add(t);
162 }
163 allSuffixesForFile = Collections.unmodifiableSet(s);
164 }
165
166 // Set up the magics list.
167 if(_allMagics == null)
168 {
169 if(magic.length() == 0)
170 { allMagics = Collections.emptySet(); }
171 else
172 { allMagics = Collections.singleton(magic); }
173 }
174 else
175 {
176 final Set<String> s = new HashSet<String>(2 + _allMagics.length*2);
177 s.add(magic); // Ensure that the primary value is present.
178 for(int i = _allMagics.length; --i >= 0; )
179 {
180 final String t = _allMagics[i];
181 if((t == null) || (t.length() < 1))
182 { throw new IllegalArgumentException(); }
183 s.add(t);
184 }
185 allMagics = Collections.unmodifiableSet(s);
186 }
187
188 // Iff no explicit handler has been supplied,
189 // and the default one appears to at least exist,
190 // use the default one.
191 if(_handlerClassName == null)
192 {
193 // Compute the name for the default handler class for this
194 // MIME type.
195 final String defaultHandlerClassName =
196 DEFAULT_HANDLER_PACKAGE + dotSuffixForInputFile.toLowerCase();
197 try {
198 // Just see if the default handler exists;
199 // report more subtle errors later.
200 Class.forName(defaultHandlerClassName);
201 // OK, seems to exist, so use it.
202 _handlerClassName = defaultHandlerClassName;
203 }
204 // Ignore errors, but don't try to use the default handler.
205 catch(final Exception e) { }
206 }
207
208 // Create instance of handler class.
209 Handler h = null;
210 try {
211 if(_handlerClassName != null)
212 {
213 h = (Handler) ((Class.forName(_handlerClassName)).newInstance());
214 }
215 }
216 // Note any errors we encounter.
217 catch(final Exception e)
218 {
219 System.err.println("ExhibitMIME: ERROR: could not create handler for MIME type "+mimeType+" with class ``"+_handlerClassName+"''.");
220 e.printStackTrace();
221 }
222 // Keep the name and handler null if not successfully instantiated.
223 handler = h;
224 handlerClassName = (handler != null) ? _handlerClassName : null;
225 }
226
227
228 /**Make meaningful human-readable diagnostic String representation.
229 * This is not intended to be machine-parsable,
230 * but does not contain whitespace if possible so it might be.
231 *
232 * @return human-readable indication of exhibit type.
233 */
234 @Override
235 public String toString()
236 {
237 final StringBuilder result = new StringBuilder(48);
238 result.append("ExhibitTypeParameters:");
239 result.append(suffixForInputFile).append('[');
240 result.append(type).append("]:");
241 result.append(mimeType);
242 return(result.toString());
243 }
244
245
246 /**ET_XXX type of file. */
247 public final int type;
248
249 /**Suffix of file if this item is an exhibit in the Gallery; lower-case, never null.
250 * This means it is an input item, and should (for example)
251 * be read-only and never deleted.
252 * <p>
253 * This does not include the dot that comes before the extension.
254 * <p>
255 * This value is never null or zero-length, and is
256 * intern()ed.
257 */
258 public final String suffixForInputFile;
259
260 /**Suffix of input file including leading dot; lower-case, never null.
261 * This is suffixForInputFile with a leading dot, and
262 * is intern()ed, so is never null and is not less than length 2.
263 */
264 public final String dotSuffixForInputFile;
265
266 /**All (dotless, lower-case) suffixes for this type, including the primary suffix; never null nor empty.
267 * We can recognise all these suffixes on input files
268 * as potentially indicating this MIME type.
269 * <p>
270 * No entries are null or "" or start with (or contain) a dot.
271 * <p>
272 * This is immutable.
273 */
274 public final Set<String> allSuffixesForFile;
275
276 /**This is the primary `magic number' at the start of the file; never null though may be "".
277 * This is really a byte string, in order, of the leading
278 * bytes we must see at the start of a file to be happy
279 * it is really the type the file suffix claims it is.
280 * If zero-length, this indicates that there is no such
281 * initial portion we can check.
282 * <p>
283 * This enables a simple form of file-type checking
284 * like the UNIX ``file'' utility.
285 * <p>
286 * This string may be zero-length, but it is never
287 * null.
288 */
289 public final String magic;
290
291 /**All legitimate `magic number' values for this type; never null.
292 * Contains the primary magic value unless it is "".
293 * <p>
294 * No entries are null or "".
295 * <p>
296 * Is empty if no magic number.
297 * <p>
298 * This is immutable.
299 */
300 public final Set<String> allMagics;
301
302 /**The canonical MIME type for this exhibit format; never null nor "".
303 * Of the form majorType/minorType eg "audio/basic" or "text/html",
304 * with all text lower-case.
305 */
306 public final String mimeType;
307
308 /**Fully-qualified name of handler class (implementing HandlerBase), or null if none.
309 */
310 public final String handlerClassName;
311
312 /**Instance of handler class, or null if none.
313 */
314 public final Handler handler;
315
316 /**Textual (English) description of the file type.
317 * This describes in human-readable terms what we
318 * expect the image to be (it may be that the file
319 * extension and magic number would allow a wider range
320 * than we describe here).
321 * <p>
322 * This is intended to be terse enough to go on a Web
323 * page with an image file if need be. It may be zero length,
324 * though must not be null.
325 */
326 public final String description;
327
328
329 /**Tested for equality and sorted on type. */
330 @Override
331 public int hashCode() { return(type); }
332
333 /**Equal if type is equal. */
334 @Override
335 public boolean equals(final Object o)
336 {
337 if(!(o instanceof ExhibitTypeParameters)) { return(false); }
338 return(type == ((ExhibitTypeParameters) o).type);
339 }
340
341 /**Sort by type. */
342 public int compareTo(final ExhibitTypeParameters o)
343 { return(type - o.type); }
344
345
346 /**Returns true if we may be able to create a thumbnail/sample with the same MIME type as this.
347 * We might fail in practice, but if this returns false we know that
348 * we definitely <em>cannot</em> make a thumbnail/sample for this
349 * exhibit type in the current execution context, eg without some
350 * loadable drivers.
351 */
352 public boolean canPossiblyCreateThumbnailOfSameMIMEType()
353 {
354 // Won't be able to do this if we don't have a handler.
355 if(handler == null) { return(false); }
356
357 // Won't be able to do this if the handler says it can't.
358 if(!handler.canMakeThumbnails()) { return(false); }
359
360 return(true); // OK, maybe we can!
361 }
362 }
363
364 // All file types are strictly positive.
365 // Existing values must not be changed as they may be persisted
366 // to long-lasting media.
367 /**Type of GIF file. */
368 public static final int ET_GIF = 1;
369 /**Type of JPEG file. */
370 public static final int ET_JPEG = 2;
371 /**Type of Sun audio (usually 64kbps 8-bit u-law G.711) file. */
372 public static final int ET_AU = 3;
373 /**Type of MPEG-1 Layer-II audio file. */
374 public static final int ET_MP2 = 4;
375 /**Type of MPEG-1 or MPEG-2 or MPEG-2.5 Layer-III audio file (8kbps and up). */
376 public static final int ET_MP3 = 5;
377 /**Type of MPEG-4 audio file (2kbps and up). */
378 public static final int ET_MP4 = 6;
379 /**Type of General MIDI audio file. */
380 public static final int ET_MIDI = 7;
381 /**Type of WAV audio file. */
382 public static final int ET_WAV = 8;
383 /**Type of HTML text fragment file. */
384 public static final int ET_HTMLFRAG = 9;
385 /**Type of MPEG video file. */
386 public static final int ET_MPEG = 10;
387 /**Type of TIFF image file. */
388 public static final int ET_TIFF = 11;
389 /**Type of BMP image file. */
390 public static final int ET_BMP = 12;
391 /**Type of PowerPoint presentation file. */
392 public static final int ET_PPT = 13;
393 /**Type of RealMedia file. */
394 public static final int ET_RM = 14;
395 /**Type of RealMedia file. */
396 public static final int ET_AVI = 15;
397 /**Type of PNG (GIF-replacement) file. */
398 public static final int ET_PNG = 16;
399 /**Type of MNG (GIF-replacement) file. */
400 public static final int ET_MNG = 17;
401 /**Type of JPEG-2000 image file. */
402 public static final int ET_JP2 = 18;
403 /**Type of Flash file. */
404 public static final int ET_SWF = 19;
405 /**Type of PDF file. */
406 public static final int ET_PDF = 20;
407 /**Type of 3GPP file. */
408 public static final int ET_3GP = 21;
409 /**Type of Windows Movie file. */
410 public static final int ET_WMV = 22;
411 /**Type of Windows Rich Text Format file. */
412 public static final int ET_RTF = 23;
413 /**Type of Gallery 'trail' marked-up text file. */
414 public static final int ET_TRML = 24;
415 /**Type of ZIP archive file. */
416 public static final int ET_ZIP = 25;
417 /**Type of GZIPped tar archive file. */
418 public static final int ET_TGZ = 26;
419 /**Type of BZIP2ed tar archive file. */
420 public static final int ET_TBZ2 = 27;
421 /**Type of QuickTime movie file. */
422 public static final int ET_MOV = 28;
423
424 /**Minimum valid ET_XXX value (these values may be sparse).
425 * Guaranteed strictly positive.
426 */
427 public static final int ET__min = ET_GIF;
428
429 /**Maximum valid ET_XXX value (these values may be sparse).
430 * Guaranteed to be no less than ET__min.
431 */
432 public static final int ET__max = ET_MOV;
433
434 /**File types accepted by (and generated by) the Gallery.
435 * Not to be handed outside the gallery in this raw state.
436 * Not necessarily in numeric order at construction here,
437 * but sorted as part of the static initialiser.
438 */
439 private static final ExhibitTypeParameters inputFileTypes[] =
440 {
441 new ExhibitTypeParameters(ET_GIF, "gif", "image/gif", null, "GIF8", "GIF image"),
442 // JPEG/JFIF would be ff d8 ff e0 XX XX 'J' 'F' 'I' 'F',
443 // but Mavica images seem to omit this JFIF header.
444 new ExhibitTypeParameters(ET_JPEG, "jpg", new String[]{"jpeg"}, "image/jpeg", null, "\u00ff\u00d8\u00ff", null, "JPEG image"),
445 new ExhibitTypeParameters(ET_AU, "au", "audio/basic", null, ".snd", "Sun audio"),
446 // MP2 magic sequence may not be long enough to be unique.
447 // See opening od -tx1 dump from an .mp2 file...
448 // 0000000 00 00 01 ba 21 00 01 00 01 80 0e 3b 00 00 01 bb
449 new ExhibitTypeParameters(ET_MP2, "mp2", "audio/mpeg", null, "\u0000\u0000\u0001\u00ba", "MP2 [MPEG-1 Layer-II] audio"),
450 // MP3 magic sequence may not be long enough to be unique.
451 // See opening od -tx1 dump from three .mp3 files...
452 // 0000000 ff f3 74 54 00 00 0c 01 2d 90 00 00 00 00 a8 02
453 // 0000000 ff e3 18 c4 00 0a 28 69 bb 21 46 00 01 00 20 78
454 // 0000000 ff fb b0 60 00 00 03 a7 5b 4c b3 09 13 f2 04 a0
455 // See http://www.mp3-tech.org/programmer/frame_header.html
456 // Header is: 11111111 111BBCCD EEEEFFGH IIJJKLMM
457 // Def BB: 00 => MPEG Version 2.5, 01 => reserved, 10 => MPEG Version 2 (ISO/IEC 13818-3), 11 => MPEG Version 1 (ISO/IEC 11172-3).
458 // So header of ff fx guarantees MPEG 1 or MPEG 2 (ie with 12-leading sync bits).
459 // Def CC: 00 => reserved, 01 => Layer III, 10 => Layer II, 11 => Layer I.
460 // Def D: 0 => protected by CRC (16 bit CRC follows header), 1 => not protected.
461 new ExhibitTypeParameters(ET_MP3, "mp3", "audio/mpeg", null, "\u00ff", "MP3 [MPEG Layer-III] audio"),
462 //new ExhibitTypeParameters(ET_MP4, "mp4", "MP4", "", "MPEG-4 audio"),
463 // MIDI magic number guessed by looking at a few files...
464 new ExhibitTypeParameters(ET_MIDI, "mid", "audio/midi", null, "MThd", "General MIDI"),
465 // WAV magic sequence may not be long enough to be unique.
466 // See opening od -cv dump from one .wav file...
467 // 0000000 R I F F 204 033 \0 \0 W A V E f m t
468 new ExhibitTypeParameters(ET_WAV, "wav", "audio/x-wav", null, "RIFF", "Wave audio"),
469 new ExhibitTypeParameters(ET_HTMLFRAG, "htxt", "text/html", null, "<", "HTML marked-up text fragment"),
470 // Deduced magic from dump (od -tx1).
471 // 0000000 00 00 01 ba 21 00 01 00 01 80 03 91 00 00 01 bb
472 new ExhibitTypeParameters(ET_MPEG, "mpg", "video/mpeg", null, "\u0000\u0000\u0001\u00ba", "MPEG video"),
473 // Deduced magic from dump (od -tx1) and RFC3302...
474 // 49 49 2a 00 (II: little endian)
475 // 4d 4d 00 2a (MM: big endian)
476 new ExhibitTypeParameters(ET_TIFF, "tif", new String[]{"tiff"}, "image/tiff", null, "II*\u0000", new String[]{"MM\u0000*"}, "TIFF image"),
477 // Deduced magic from dump (od -tx1).
478 // 0000000 42 4d 76 b6 0f 00 00 00 00 00 36 00 00 00 28 00
479 new ExhibitTypeParameters(ET_BMP, "bmp", "image/bmp", null, "BM", "BMP image"),
480 // Deduced magic from dump (od -tx1).
481 // 0000000 d0 cf 11 e0 a1 b1 1a e1 00 00 00 00 00 00 00 00
482 new ExhibitTypeParameters(ET_PPT, "ppt", "application/powerpoint", null, "\u00d0\u00cf\u0011\u00e0\u00a1\u00b1\u001a\u00e1\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", "PowerPoint presentation"),
483 // 0000000 2e 52 4d 46 00 00 00 12 00 01 00 00 00 00 00 00
484 new ExhibitTypeParameters(ET_RM, "rm", "application/vnd.rn-realmedia", null, ".RMF", "RealMedia audio/video"),
485 // Magic number as suggest by http://www.jmcgowan.com/avi.html: "RIFF" <4-byte-length> "AVI ".
486 // See also http://www.fourcc.org/.
487 new ExhibitTypeParameters(ET_AVI, "avi", "video/avi", null, "RIFF", "AVI audio/video"),
488 // Magic number from formal tech documentation.
489 // See http://www.libpng.org/.
490 new ExhibitTypeParameters(ET_PNG, "png", "image/png", null, "\u0089PNG\r\n\u001a\n", "PNG image"),
491 // Magic number from formal tech documentation.
492 // See http://www.libpng.org/.
493 new ExhibitTypeParameters(ET_MNG, "mng", "video/x-mng", null, "\u008aMNG\r\n\u001a\n", "MNG animation/video"),
494 // JPEG-2000; 12-byte magic number from spec; also see RFC3745 and jpeg.org: 0000 000C 6A50 2020 0D0A 870A.
495 new ExhibitTypeParameters(ET_JP2, "jp2", "image/jp2", null, "\u0000\u0000\u0000\u000cjP \r\n\u0087\n", "JPEG 2000 image"),
496 // Macromedia Flash: magic number as per: http://www.garykessler.net/library/file_sigs.html
497 new ExhibitTypeParameters(ET_SWF, "swf", null, "application/x-shockwave-flash", null, "CWS", new String[]{"FWS"}, "Macromedia ShockWave Flash"),
498 // Adobe PDF: magic number as per: http://www.garykessler.net/library/file_sigs.html
499 new ExhibitTypeParameters(ET_PDF, "pdf", "application/pdf", null, "%PDF", "Adobe Portable Document Format"),
500 // See RCF3839 at http://www.faqs.org/rfcs/rfc3839.html
501 new ExhibitTypeParameters(ET_3GP, "3gp", new String[]{"3gpp"}, "video/3gpp", null, "", null, "3G mobile audio/video"),
502 // Windows Movie.
503 // Deduced magic from dump (od -tx1).
504 // 0000000 30 26 b2 75 8e 66 cf 11 a6 d9 00 aa 00 62 ce 6c
505 new ExhibitTypeParameters(ET_WMV, "wmv", null, "video/x-ms-wmv", null, "0&", null, "Windows movie"),
506 // Windows Rich Text Format.
507 new ExhibitTypeParameters(ET_RTF, "rtf", null, "application/rtf", null, "{\\rtf1", null, "Windows Rich Text Format"),
508 // Gallery tail mark-up language; opening line must be "@ " followed by title text.
509 // File content is strictly 7-bit, but may contain well-formed XHTML 1.0 markup including UNICODE character entities,
510 // ie should be suitable for insertion in XHTML and HTML UTF-8 documents.
511 new ExhibitTypeParameters(ET_TRML, "trml", null, "text/x-pg2k-trail", null, "@ ", null, "Gallery TRail Markup Language"),
512 // ZIP archive. Magic number 50 4B 03 04 (PK..)
513 new ExhibitTypeParameters(ET_ZIP, "zip", null, "application/zip", null, "PK\u0003\u0004", null, "ZIP archive"),
514 // GZIPped tar archive. Magic number 1F 8B 08
515 new ExhibitTypeParameters(ET_TGZ, "tgz", null, "application/x-tgz", null, "\u001f\u008b\u0008", null, "GZIPped tar archive"),
516 // BZIP2ed tar archive. Magic number "BZh"
517 new ExhibitTypeParameters(ET_TBZ2, "tbz2", null, "application/x-tbz2", null, "BZh", null, "BZIP2ed tar archive"),
518 // Apple QuickTime movie. No consistent magic number as such.
519 new ExhibitTypeParameters(ET_MOV, "mov", null, "video/quicktime", null, "", null, "QuickTime movie"),
520 };
521
522 /**Sort in situ by type as needed. */
523 static { Arrays.sort(inputFileTypes); }
524
525 /**Returns a (private) list of all valid exhibit types, in order by type. */
526 public static ExhibitTypeParameters[] getAllValidExhibitTypes()
527 { return(inputFileTypes.clone()); }
528
529 /**Checks if the extension of the (putative) exhibit name is a recognised input extension.
530 * The string should not be null, nor start with a dot.
531 * (A null input gives a null return.)
532 * <p>
533 * This returns null if the extension is not recognised,
534 * else the (immutable) ExhibitTypeParameters describing it.
535 */
536 public static ExhibitTypeParameters isValidInputExhibitNameExtension(
537 final CharSequence extension)
538 {
539 if(extension == null) { return(null); } // No extension at all!
540 final String extensionS = extension.toString();
541 final ExhibitTypeParameters result =
542 _iVIFE_cache.get(extensionS);
543 if(result != null) { return(result); } // All is well.
544 // Table might not have been initialised...
545 synchronized(_iVIFE_cache)
546 {
547 if(_iVIFE_cache.size() == 0)
548 {
549 for(int i = inputFileTypes.length; --i >= 0; )
550 {
551 _iVIFE_cache.put(inputFileTypes[i].suffixForInputFile,
552 inputFileTypes[i]);
553 }
554 return(_iVIFE_cache.get(extensionS));
555 }
556 }
557 return(null); // Failed; not a valid extension.
558 }
559 /**Private cache of mappings from extension to parameters.
560 * Used by isValidInputExhibitNameExtension(), and filled in under
561 * protection of its own lock on first use if zero-sized.
562 * <p>
563 * Fixed small size.
564 */
565 private static final Hashtable<String,ExhibitTypeParameters> _iVIFE_cache =
566 new Hashtable<String, ExhibitTypeParameters>(inputFileTypes.length * 2 + 1);
567
568 /**Gets type of an exhibit given its extension.
569 * The string passed must not be null, nor start with a dot.
570 * <p>
571 * This returns null if the extension is not recognised,
572 * else the (immutable) ExhibitTypeParameters describing the exhibit type.
573 * <p>
574 * This checks first by the primary suffix for each exhibit type,
575 * but if that fails may check by secondary suffix.
576 */
577 public static ExhibitTypeParameters getExhibitType(final String extension)
578 {
579 final ExhibitTypeParameters result =
580 _gET_cache.get(extension);
581 if(result != null) { return(result); } // All is well.
582 // Table might not have been initialised...
583 synchronized(_gET_cache)
584 {
585 if(_gET_cache.size() == 0)
586 {
587 for(int i = inputFileTypes.length; --i >= 0; )
588 {
589 _gET_cache.put(inputFileTypes[i].suffixForInputFile,
590 inputFileTypes[i]);
591 }
592 return(_gET_cache.get(extension));
593 }
594 }
595 return(null); // Failed; not a valid extension.
596 }
597 /**Private cache of mappings from extension to parameters.
598 * Used by isValidInputExhibitNameExtension(), and filled in under
599 * protection of its own lock on first use if zero-sized.
600 * <p>
601 * Fixed small size.
602 */
603 private static final Hashtable<String,ExhibitTypeParameters> _gET_cache =
604 new Hashtable<String, ExhibitTypeParameters>(inputFileTypes.length * 2 + 1);
605
606 /**Verifies the magic number for a given exhibit.
607 * This will try to read enough data to check the magic number.
608 * <p>
609 * Returns false if the magic number check fails.
610 * <p>
611 * If the file type specified has no unique magic number to
612 * be checked this always succeeds.
613 * <p>
614 * This function does not close the input stream.
615 *
616 * @param etp is the exhibit type (must not be null)
617 * @param exhibitByteStream is a stream of bytes from the
618 * start of the exhibit at least long enough to contain the
619 * magic number
620 */
621 public static boolean magicOK(final ExhibitTypeParameters etp,
622 final InputStream exhibitByteStream)
623 {
624 assert((etp != null) && (exhibitByteStream != null));
625
626 // Now check the magic number if any...
627 final int numMagics = etp.allMagics.size();
628 if(numMagics == 0) { return(true); } // No magic to check...
629
630 // Compute longest magic number/sequence for this file type.
631 int magicLength = etp.magic.length();
632 if(numMagics > 1)
633 {
634 for(final String s : etp.allMagics)
635 { if(s.length() > magicLength) { magicLength = s.length(); } }
636 }
637
638 final DataInputStream dis; // = null;
639 try
640 {
641 // OK, attempt to open the stream and read enough
642 // bytes to check the magic...
643 // (If the stream is not long enough we expect
644 // to get an EOFException thrown.)
645 dis = new DataInputStream(exhibitByteStream);
646 final byte data[] = new byte[magicLength];
647 dis.readFully(data);
648
649 // If any magic matches then we are OK...
650 nextMagic: for(final String s : etp.allMagics)
651 {
652 for(int i = s.length(); --i >= 0; )
653 {
654 if(s.charAt(i) != (0xff & data[i]))
655 { continue nextMagic; } // Match failed...
656 }
657 return(true); // Match succeeded on entire magic sequence...
658 }
659 return(false); // Did not find a magic number to match.
660 }
661 catch(final IOException e) { return(false); } // Failed...
662 }
663
664 /**Checks that file has expected magic number; throws IOException if not.
665 * @throws InvalidObjectException if given file does not have
666 * a correct magic number
667 * @throws IOException in case of difficulty reading the file
668 */
669 public static void checkMagicOK(final ExhibitTypeParameters etp, final File file)
670 throws IOException,
671 InvalidObjectException
672 {
673 final FileInputStream fis = new FileInputStream(file);
674 try {
675 if(!ExhibitMIME.magicOK(etp, fis))
676 { throw new InvalidObjectException("bad magic number: wrong file type"); }
677 }
678 finally { fis.close(); }
679 }
680
681 /**The longest known magic number in bytes of any exhibit type supported; strictly positive once computed.
682 * Volatile to allow access without a lock.
683 * <p>
684 * Computed on first use.
685 */
686 private static volatile int longestMagicBytes;
687
688 /**Get longest known magic number in bytes of any exhibit type supported; strictly positive. */
689 public static int getLongestMagicBytes()
690 {
691 if(longestMagicBytes == 0)
692 {
693 int longest = 0;
694 for(final ExhibitTypeParameters etp : inputFileTypes)
695 {
696 for(final String m : etp.allMagics)
697 {
698 final int mLen = m.length();
699 if(mLen > longest) { longest = mLen; }
700 }
701 }
702 longestMagicBytes = longest;
703 assert(longestMagicBytes > 0);
704 }
705 return(longestMagicBytes);
706 }
707
708
709 /**Guesses the type of a file/exhibit from its magic number, or null if unrecognisable.
710 * This may only be able to check for files that have a unique magic number.
711 * <p>
712 * The stream may need to be markable to allow various possibilities
713 * to be tested.
714 */
715 public static ExhibitTypeParameters guessTypeFromMagic(final InputStream is)
716 throws IOException
717 {
718 if(is == null) { throw new IllegalArgumentException(); }
719
720 // Read in enough bytes for the longest-known magic number
721 // if possible.
722 final byte buf[] = new byte[getLongestMagicBytes()];
723 int n = 0; // Bytes read so far.
724 while(n < buf.length)
725 {
726 final int r = is.read(buf, n, buf.length - n);
727 if(r < 1) { break; }
728 n += r;
729 }
730
731 for(final ExhibitTypeParameters etp : inputFileTypes)
732 {
733 if(etp.allMagics.size() == 0)
734 { continue; /* No magic number(s) to test for. */ }
735
736 for(final String s : etp.allMagics)
737 {
738 final int mLen = s.length();
739 if(mLen == 0)
740 {
741 if(mLen > n)
742 { continue; /* File too short to be this type. */ }
743 }
744 boolean isOK = true;
745 for(int i = mLen; --i >= 0; )
746 {
747 // A single mismatched byte means not this type...
748 if(((byte) s.charAt(i)) != buf[i])
749 {
750 isOK = false;
751 break;
752 }
753 }
754
755 // Return immediately if we found a match...
756 if(isOK)
757 { return(etp); }
758 }
759 }
760
761 return(null); // Couldn't recognise it.
762 }
763
764 /**Gets the type of a file from its name, or null if unrecognised here.
765 * The string passed must not be null, nor start with a dot.
766 * <p>
767 * This returns null if the extension is not recognised for an exhibit,
768 * else returns the (immutable) ExhibitTypeParameters describing it.
769 * <p>
770 * The extension should be for an exhibit input,
771 * though this can be called to quickly check if a filename or URI
772 * might be for an exhibit, returning null if not.
773 *
774 * @param name name of file or URL/URI
775 */
776 public static ExhibitTypeParameters getInputFileType(final CharSequence name)
777 {
778 return(isValidInputExhibitNameExtension(FileTools.getExtension(name)));
779 }
780
781 /**Given an ET_XXX type, returns the ExhibitTypeParameters.
782 * Throws IllegalArgumentException for an illegal type.
783 */
784 public static synchronized ExhibitTypeParameters getParamsByType(final int type)
785 {
786 if(_gFPBT_cache == null)
787 {
788 _gFPBT_cache = new ExhibitTypeParameters[ET__max+1];
789 for(int i = inputFileTypes.length; --i >= 0; )
790 {
791 final ExhibitTypeParameters ifp = inputFileTypes[i];
792 _gFPBT_cache[ifp.type] = ifp;
793 }
794 }
795 if((type < ET__min) || (type > ET__max))
796 { throw new IllegalArgumentException("bad type"); }
797 final ExhibitTypeParameters result = _gFPBT_cache[type];
798 if(result == null)
799 { throw new IllegalArgumentException("bad type"); }
800 return(result);
801 }
802 /**Private cache of mappings from type to parameters.
803 * Constructed on first use by getParamsByType().
804 * <p>
805 * Fixed small size.
806 */
807 private static ExhibitTypeParameters _gFPBT_cache[];
808
809 /**Get the MIME type of an exhibit (even at the end of filename or URI or URL) from its name; never null nor "".
810 */
811 public static String getMIMEType(final CharSequence name)
812 throws IOException
813 {
814 final String extension = FileTools.getExtension(name);
815 final ExhibitTypeParameters etp = getExhibitType(extension);
816 if(etp == null) // Can only happen if we stop recognising something already in the DB.
817 { throw(new IOException("unknown MIME type for ``"+extension+"''")); }
818 return(etp.mimeType);
819 }
820 }