/* * [MimeCheck.java] * * Summary: Checks the MIME type being sent by a server for a given URL is correct. * * Copyright: (c) 2004-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.6 2004-05-21 added about box changed Mime type for * 1.7 2004-05-25 added more MS mime types version * 1.8 2004-05-25 added more mime types * 1.9 2004-05-25 changed example url, fixed size * 2.0 2005-01-01 ANT build * 2.1 2005-08-30 Microsoft excel extensions. * 2.2 2005-08-30 more mime types * 2.3 2005-08-30 add Javadoc * 2.4 2006-01-01 add rss * 2.5 2006-01-13 add jp2 * 2.6 2006-01-13 * 2.7 2006-03-06 reformat with IntelliJ, add Javadoc * 2.8 2007-04-11 add chm and hlp * 2.9 2007-06-16 switch to Swing JDK 1.5, add ttf * 3.0 2007-08-29 add flv extension support * 3.1 2007-09-04 add about 60 new mime types. Helper code to add new ones. * 3.2 2007-12-08 fix mime type for zip * 3.3 2008-02-01 allow multiple MIMEs per extension. * 3.4 2008-02-20 add QuickTime aac MP4 used in iPods, Silverlight and misc other audio formats. * 3.5 2008-02-29 validation checks on tables, remove dups. * 3.6 2008-04-03 add build to title. * 3.7 2008-08-06 add various certificate types * 3.8 2008-11-13 add pps mime type, generate core website list, validate database. * 3.9 2008-11-13 put mime database is a serialised resource in the jar * 4.0 2008-11-20 use DataOutputStream to serialise binary version of mimes database. * HTML and Java generated from a common database * 4.1 2009-09-02 add downloadable font MIMEs. * 4.2 2009-11-11 messages for file not found * 4.3 2009-11-30 add Open Office mime types * 4.4 2010-03-24 user selectable look and feel * 4.5 2011-10-31 comprehensible error message if you refuse to grant permission. */ package com.mindprod.mimecheck; import com.mindprod.common18.Build; import com.mindprod.common18.CMPAboutJBox; import com.mindprod.common18.CheckPermission; import com.mindprod.common18.EIO; import com.mindprod.common18.FontFactory; import com.mindprod.common18.HybridJ; import com.mindprod.common18.JEButton; import com.mindprod.common18.Laf; import com.mindprod.common18.VersionCheck; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JTextArea; import javax.swing.JTextField; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Insets; import java.awt.MediaTracker; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.ImageProducer; import java.io.DataInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketPermission; import java.net.URL; import java.util.HashMap; import java.util.PropertyPermission; import static java.lang.System.*; /** * Checks the MIME type being sent by a server for a given URL is correct. *

* The most common use it to make sure JNLP files are * coming back at Also accepts a <PARAM name="URL" value="xxxxx"> for an automatically tested * url * * @author Roedy Green, Canadian Mind Products * @version 4.5 2011-10-31 comprehensible error message if you refuse to grant permission. * @since 2004 */ public final class MimeCheck extends JApplet implements Runnable { /** * height of Applet box in pixels. Does not include surrounding frame. */ private static final int APPLET_HEIGHT = 385; /** * Width of Applet box in pixels. if you change size, fix E:/com/mindprod/mimecheck.js * E:/mindprod/applet/mimecheck.html E:/mindprod/applet/mimecheck.js E:/mindprod/applet/urlcheck.html * E:/mindprod/jgloss/mime.html */ private static final int APPLET_WIDTH = 672; private static final int FIRST_COPYRIGHT_YEAR = 2004; /** * undisplayed copyright notice */ @SuppressWarnings( { "UnusedDeclaration" } ) private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2004-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; private static final String RELEASE_DATE = "2011-10-31"; private static final String TITLE_STRING = "Mime Check"; private static final String VERSION_STRING = "4.5"; /** * background for entire applet, pale green */ private static final Color BACKGROUND_FOR_APPLET = Build.BACKGROUND_FOR_BLENDING; /** * background of field where enter data. */ private static final Color BACKGROUND_FOR_FIELD = Color.WHITE; /** * result background, pale blue */ private static final Color BACKGROUND_FOR_RESULT = new Color( 0xf6ffff ); private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x008000 ); /** * colour for label text */ private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 ); /** * colour of text */ private static final Color FOREGROUND_FOR_TEXT = Color.blue; /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 ); /** * for for title second line */ private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 ); /** * usual font */ private static final Font FONT_FOR_USUAL = FontFactory.build( "Dialog", Font.PLAIN, 15 ); /** * map extension to mime type */ private static HashMap eToM; private ImageIcon greenBall; private ImageIcon redBall; /** * trigger test of server */ private JButton testButton; private JLabel ball; /** * instructions for use */ private JLabel instructions; /** * label for proper mime type */ private JLabel properMimeTypeLabel; /** * label for server's mime type */ private JLabel serverMimeTypeLabel; /** * title */ private JLabel title; /** * title, second line */ private JLabel title2; /** * label for URL */ private JLabel urlLabel; /** * what the mime type should be */ private JTextArea properMimeTypes; /** * what mime type the server returned */ private JTextField serverMimeType; /** * where user keys in URL to check */ private JTextField urlField; /** * url of document whose mime type you want to check */ private URL url; /** * are we running this as applet=true or application=false */ private boolean asApplet = true; /** * constructor */ public MimeCheck() { } /** * constructor * * @param asApplet true if running as Applet as opposed to application */ @SuppressWarnings( { "SameParameterValue" } ) public MimeCheck( boolean asApplet ) { this.asApplet = asApplet; } /** * Read a png, gif or jpg from the archive resource jar file. * * @param imageResourceName fully resource name of the image in the jar. if leave off off lead / will be relative to * com.mindprod.example. * @param target Component where this image will end up. * * @return ImageIcon corresponding to the png/gif/jpg. */ @SuppressWarnings( { "EmptyCatchBlock" } ) private static ImageIcon createImageIcon( String imageResourceName, Component target ) { try { // default package com.mindprod.mimecheck URL url = MimeCheck.class.getResource( imageResourceName ); if ( url == null ) { err.println( "createImageIcon cannot find resource " + imageResourceName ); return null; } Image image = target.getToolkit().createImage( ( ImageProducer ) url.getContent() ); // wait till it is loaded MediaTracker tracker; try { // wait until image is fully loaded. // MediaTracker arranges repaint via ImageObserver interface tracker = new MediaTracker( target ); tracker.addImage( image, 0 ); tracker.waitForID( 0 ); } catch ( InterruptedException e ) { } return new ImageIcon( image ); } catch ( IOException e ) { err.println(); e.printStackTrace( err ); err.println(); return null; } } // end createImageIcon /** * Get proper MIME type(s) that belongs with this URL. * * @param url of a document of some kind we will fetch to see what MIME type the server provides with it. * * @return Proper associated mime types */ private static String[] getProperMimes( URL url ) { final String file = url.getFile();// lyrics.txt if ( file == null || file.length() == 0 ) { return new String[] { "no file in the url, can't guess expected MIME" }; } final int place = file.lastIndexOf( '.' ); if ( place < 0 ) { return new String[] { "no .extension, can't guess expected MIME" }; } final String[] mimes = getProperMimes( file.substring( place + 1 ) .toLowerCase() ); if ( mimes == null ) { return new String[] { "unknown .extension, unknown MIME" }; } else { return mimes; } } /** * Get associated mimes for a given extension. * * @param extension usually 3 letters, lower case * * @return array of associated mimes, null if no match, possibly [1]. */ private static String[] getProperMimes( final String extension ) { return eToM.get( extension ); } /** * build a menu with Look & Feel and About across the top */ private void buildMenu() { // turn on anti-alias System.setProperty( "swing.aatext", "true" ); final JMenuBar menubar = new JMenuBar(); setJMenuBar( menubar ); final JMenu lafMenu = Laf.buildLookAndFeelMenu(); if ( lafMenu != null ) { menubar.add( lafMenu ); } final JMenu menuHelp = new JMenu( "Help" ); menubar.add( menuHelp ); final JMenuItem aboutItem = new JMenuItem( "About" ); menuHelp.add( aboutItem ); aboutItem.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e ) { // open about frame new CMPAboutJBox( TITLE_STRING, VERSION_STRING, "Shows MIME types various servers are sending,", "", "freeware", RELEASE_DATE, FIRST_COPYRIGHT_YEAR, "Roedy Green", "MIMECHECK", "1.8" ); } } ); } /** * construct all the components */ private void createComponents() { title = new JLabel( TITLE_STRING + " " + VERSION_STRING, JLabel.LEFT ); title.setFont( FONT_FOR_TITLE ); title.setForeground( FOREGROUND_FOR_TITLE ); title2 = new JLabel( "released:" + RELEASE_DATE + " build:" + Build.BUILD_NUMBER ); title2.setFont( FONT_FOR_TITLE2 ); title2.setForeground( FOREGROUND_FOR_TITLE ); urlLabel = new JLabel( "url" ); urlLabel.setFont( FONT_FOR_USUAL ); urlLabel.setForeground( FOREGROUND_FOR_LABEL ); urlField = new JTextField( "http://mindprod.com/webstart/setclock.jnlp" ); urlField.setEditable( true ); urlField.setMargin( new Insets( 2, 2, 2, 2 ) ); urlField.setFont( FONT_FOR_USUAL ); urlField.setForeground( FOREGROUND_FOR_TEXT ); urlField.setBackground( BACKGROUND_FOR_FIELD ); serverMimeTypeLabel = new JLabel( "server MIME type" ); serverMimeTypeLabel.setFont( FONT_FOR_USUAL ); serverMimeTypeLabel.setForeground( FOREGROUND_FOR_LABEL ); serverMimeType = new JTextField( "application/x-java-jnlp-file" ); serverMimeType.setEditable( false ); serverMimeType.setMargin( new Insets( 2, 2, 2, 2 ) ); serverMimeType.setFont( FONT_FOR_USUAL ); serverMimeType.setForeground( FOREGROUND_FOR_TITLE ); serverMimeType.setBackground( BACKGROUND_FOR_RESULT ); properMimeTypeLabel = new JLabel( "proper MIME type" ); properMimeTypeLabel.setFont( FONT_FOR_USUAL ); properMimeTypeLabel.setForeground( FOREGROUND_FOR_LABEL ); properMimeTypes = new JTextArea( "application/x-java-jnlp-file" ); properMimeTypes.setEditable( false ); properMimeTypes.setMargin( new Insets( 2, 2, 2, 2 ) ); properMimeTypes.setFont( FONT_FOR_USUAL ); properMimeTypes.setForeground( FOREGROUND_FOR_TITLE ); properMimeTypes.setBackground( BACKGROUND_FOR_RESULT ); ball = new JLabel(); redBall = createImageIcon( "redball.png", ball ); greenBall = createImageIcon( "greenball.png", ball ); ball.setIcon( greenBall ); instructions = new JLabel( "Enter URL of a document you want to check, and hit TEST." ); instructions.setFont( FontFactory.build( "Dialog", Font.PLAIN, 13 ) ); instructions.setForeground( FOREGROUND_FOR_INSTRUCTIONS ); testButton = new JEButton( "Test" ); testButton.setToolTipText( "Test if the server is issuing the correct MIME type" ); } /** * check the current URL with the server. * * @param url URL to check, http: or https: * * @return MIME type of that URL that the server sent. aka content-type */ private String getMimeTypeFromServer( URL url ) { try { if ( !CheckPermission.doWeHavePermissionTo( "talk to website: " + url.toString(), new SocketPermission( url.getHost(), "connect" ), this ) ) { return "unknown"; } // O P E N // Generate an HTTP GET Command // urlc will contain subclasses of URLConnection like: // http: HttpURLConnection // https: HttpsURLConnectionImpl // file: FileURLConnection final HttpURLConnection urlc = ( HttpURLConnection ) url.openConnection(); if ( urlc == null ) { return "Error: Unable to make a connection"; } // Use GET instead of HEAD. Otherwise server might not bother to // tell us. urlc.setRequestMethod( "GET" ); // turn off keep-alive urlc.setRequestProperty( "Connection", "close" ); urlc.setAllowUserInteraction( false ); urlc.setDoInput( true ); urlc.setDoOutput( false ); urlc.setUseCaches( false ); urlc.connect(); // ignored if already connected. int responseCode = urlc.getResponseCode(); if ( responseCode != HttpURLConnection.HTTP_OK ) { return "Error: response code " + responseCode + " : " + urlc.getResponseMessage(); } final String mime = urlc.getContentType(); if ( mime == null || mime.length() == 0 ) { return "no MIME code specified"; } urlc.disconnect(); return mime; } catch ( IOException e ) { return "Error: server not responding."; } } /** * read is binary mime data resource into a HashMap */ private void getMimesDat() { try { // This is effectively reading a serialised HashMap, but generics makes that impossible to code // simply, so we use a DataInputStream instead. // O P E N // look in jar for com.mindprod.mimecheck.mimes.dat DataInputStream dis = EIO.getDataInputStream( MimeCheck.class.getResourceAsStream( "mimes.dat" ), 8 * 1024 ); // stream has count of entries, then pairs, string extension, count of mimems, string[] mime types int recCount = dis.readInt(); eToM = new HashMap<>( ( recCount * 150 ) / 100 ); for ( int rec = 0; rec < recCount; rec++ ) { // R E A D one rec String ext = dis.readUTF(); int mimeCount = dis.readInt(); String[] mimes = new String[ mimeCount ]; for ( int i = 0; i < mimeCount; i++ ) { mimes[ i ] = dis.readUTF(); } eToM.put( ext, mimes ); } // C L O S E dis.close(); } catch ( Exception e ) { throw new IllegalArgumentException( "Unable to retrieve mime database resource in jar" ); } } /** * hook up listeners */ private void hookComponents() { testButton.addActionListener( new ActionListener() { /** * check the mime type when the user clicks test */ public void actionPerformed( ActionEvent e ) { Object object = e.getSource(); if ( object == testButton ) { test(); } // end if } // end actionPerformed } // end anonymous class );// end addActionListener line } /** * layout components of the gridbag * * @param contentPane where to add components. */ private void layoutComponents( Container contentPane ) { // layout // ---0------1 --------------2 -- // --Title-- -title2------------ 0 // url: ----url--------- -------1 // server: -- server------- ball 2 // proper: -- proper-------- ---3 // ------instructions------- TEST 4 // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title, new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 10, 10, 5, 5 ), 0, 0 ) ); contentPane.add( title2, new GridBagConstraints( 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 10, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( urlLabel, new GridBagConstraints( 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 5, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( serverMimeTypeLabel, new GridBagConstraints( 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 5, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( properMimeTypeLabel, new GridBagConstraints( 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets( 5, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( urlField, new GridBagConstraints( 1, 1, 1, 1, 100.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets( 5, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( serverMimeType, new GridBagConstraints( 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets( 5, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( ball, new GridBagConstraints( 2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 5, 5, 5, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( properMimeTypes, new GridBagConstraints( 1, 3, 1, 1, 0.0, 100.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets( 5, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( instructions, new GridBagConstraints( 0, 4, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets( 5, 10, 10, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( testButton, new GridBagConstraints( 2, 4, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 5, 5, 10, 10 ), 0, 0 ) ); } /** * test the current URL */ private void test() { url = null; try { url = new URL( urlField.getText() ); } catch ( MalformedURLException e ) { properMimeTypes.setText( "malformed URL" ); serverMimeType.setText( "malformed URL" ); ball.setIcon( redBall ); return; } final String[] properMimes = getProperMimes( url ); final StringBuilder sb = new StringBuilder( 500 ); for ( String mime : properMimes ) { sb.append( mime ); sb.append( '\n' ); } properMimeTypes.setText( sb.toString() ); serverMimeTypeLabel.setText( "server MIME type" + ( properMimes.length > 1 ? "s" : "" ) ); testButton.setEnabled( false ); serverMimeType.setText( "checking with the server..." ); serverMimeType.setBackground( Color.WHITE ); repaint(); Thread.yield(); // get stuff from server on separate thread // to give AWT time to repaint. new Thread( this ).start(); } /** * Allow this Applet to run as as application as well. * * @param args command line arguments ignored. */ public static void main( String args[] ) { HybridJ.fireup( new MimeCheck( false ), TITLE_STRING + " " + VERSION_STRING, APPLET_WIDTH, APPLET_HEIGHT ); } // end main /** * Called by the browser or Applet viewer to inform * this Applet that it has been loaded into the system. */ @Override public void init() { if ( !VersionCheck.isJavaVersionOK( 1, 8, 0, this ) ) { // effectively abort return; } // check ok to set LAF if ( !CheckPermission.doWeHavePermissionTo( "set the look and feel", new PropertyPermission( "swing.aatext", "write" ), this ) ) { return; } getMimesDat(); buildMenu(); // also initial L&F Container contentPane = this.getContentPane(); contentPane.setBackground( BACKGROUND_FOR_APPLET ); contentPane.setLayout( new GridBagLayout() ); createComponents(); layoutComponents( contentPane ); hookComponents(); if ( asApplet ) { String defaultURL = getParameter( "URL" ); if ( defaultURL != null && defaultURL.length() != 0 ) { urlField.setText( defaultURL ); test(); } } this.validate(); this.setVisible( true ); } // end init /** * get mime type from server on separate thread */ public void run() { // e.g. text/html; charset=utf-8 String serverMime = getMimeTypeFromServer( url ); out.println( serverMime ); serverMimeType.setText( serverMime ); serverMimeType.setBackground( BACKGROUND_FOR_RESULT ); // ignore anything after ; or later final int place = serverMime.indexOf( ';' ); if ( place >= 0 ) { serverMime = serverMime.substring( 0, place ).trim(); } boolean correct = false; final String[] expectedMimes = getProperMimes( url ); for ( String expectedMime : expectedMimes ) { if ( serverMime.equals( expectedMime ) ) { correct = true; break; } } ball.setIcon( correct ? greenBall : redBall ); testButton.setEnabled( true ); repaint(); } }