/*
* [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();
}
}