/* * [FileTransfer.java] * * Summary: classes to copy, and download files. * * Copyright: (c) 1999-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.2 1999-10-26 split off from FileTransfer * 1.3 1999-10-28 add safety code when length specified * is -1 or 0, to copy as if the length were unknown. * 1.4 1999-10-29 ensure every file closed, including ZipFile * safety check for null parms. * 1.5 2001-01-23 use more elegant * reads in the while loops instead of breaks. * 1.6 2002-04-22 conforming package name. * 1.7 2003-09-15 add closeTarget parameter to many methods * rename download( InputStream, File ) to copy( InputStream , File ) * add copy( File, OutputStream ) * 1.8 2006-01-10 add Download class. * 1.9 2006-02-05 reformat with IntelliJ, add Javadoc * 2.0 2007-05-17 add pad and icon * 2.1 2007-08-04 convert to JDK 1.5, * add timeout support * add sleep to avoid hogging CPU on stall * add support for preferred MIME types on download. * 2.2 2007-08-24 use http Read methods. * 2.3 2007-09-26 add timeout. * 2.4 2008-07-05 add append method. * 2.5 2008-08-10 add setReadTimeout and setConnectTimeout methods. * 2.6 2015-11-26 add error message when download fails. */ package com.mindprod.filetransfer; import com.mindprod.common18.Build; import com.mindprod.http.Read; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Locale; import static java.lang.System.*; /** * classes to copy, and download files. *

* To read or write from the client's local hard disk, you will need a signed Applet and * security clearance. see Signed Applet in the Java glossary. To read a files from the server, the file must be given * public read access, usually the default. To write a file to the server, you server will have to support CGI-PUT with * public access. This is unusual to find. Normally you upload files with FTP. See FTP in the Java glossary. * See also the simpler hunkio package. * * @author Roedy Green, Canadian Mind Products * @version 2.6 2015-11-26 add error message when download fails. * @since 1999 */ public class FileTransfer extends MiniFileTransfer { private static final int FIRST_COPYRIGHT_YEAR = 1999; /** * Accept-Charset for header */ private static final String ACCEPT = "iso-8859-1, utf-8, utf-16, *;q=0.1"; /** * Accept-Encoding for header. */ private static final String ACCEPT_ENCODING = "identity"; /** * undisplayed copyright notice */ @SuppressWarnings( { "UnusedDeclaration" } ) private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 1999-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; @SuppressWarnings( { "UnusedDeclaration" } ) private static final String RELEASE_DATE = "2015-11-30"; /** * embedded version string. */ @SuppressWarnings( { "UnusedDeclaration" } ) private static final String VERSION_STRING = "2.6"; /*** * why we failed */ static String why = null; static { System.setProperty( "java.net.preferIPv4Stack", "true" ); System.setProperty( "jsse.enableSNIExtension", "false" ); System.setProperty( "jdk.tls.ephemeralDHKeySize", "2048" ); } /** * constructor */ @SuppressWarnings( { "WeakerAccess" } ) public FileTransfer() { super(); } /** * constructor * * @param buffSize how big the I/O chunks are to copy files. */ public FileTransfer( final int buffSize ) { super( buffSize ); } /** * Copy a file from one spot on hard disk to another. * * @param source file to copy on local hard disk. * @param target new file to be created on local hard disk. * * @return true if the copy was successful. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion" } ) boolean copy( final File source, final File target ) { if ( source == null ) { why = "null source"; return false; } if ( target == null ) { why = "null target"; return false; } try { // O P E N FileInputStream is = new FileInputStream( source ); FileOutputStream os = new FileOutputStream( target ); // C O P Y _ S O U R C E _ T O _ T A R G E T long fileLength = source.length(); // C L O S E // handled by inner copy return copy( is, os, fileLength, true ); } catch ( IOException e ) { why = e.getMessage(); return false; } } // end copy /** * set up the standard properties on the connection. * Like code in com.mindprod.http.Http. * * @param urlc Connection we are setting up. */ protected void setStandardProperties( final URLConnection urlc ) { urlc.setConnectTimeout( connectTimeout ); urlc.setReadTimeout( readTimeout ); urlc.setRequestProperty( "User-Agent", Build.USER_AGENT ); // no referrer urlc.setRequestProperty( "Accept", Build.ACCEPT_MIMES ); // firefox send text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 urlc.setRequestProperty( "Accept-Charset", Build.ACCEPT_CHARSET ); // no deflate, could be added later if we can find code to handle it. urlc.setRequestProperty( "Accept-Encoding", ACCEPT_ENCODING ); // relaxed, prefer English final Locale locale = Locale.getDefault(); // e.g. en-CA;q=1,en;q=.8, canadian, but if not available use English. // firefox uses en-US,en;q=0.5 urlc.setRequestProperty( "Accept-Language", locale.toString().replace( '_', '-' ) + ";q=1," + locale.getLanguage() + ";q=.8" ); } /** * dummy main * * @param args not used source url, target file */ public static void main( final String[] args ) { err.println( "FileTransfer is not intended to be run from the command line. See Download." ); } /** * append a file onto the end of another. Works with pure bytes, so can handle binary and text files. * Does not do anything special to remove ^Z when appending. You can concatenate files with multiple calls * to append. * * @param source file to copy on local hard disk. * @param target file that will become larger by having source appended to the end of it. * It need not exist. Source and target must be using the same encoding. * * @return true if the copy was successful. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion", "UnusedDeclaration" } ) public boolean append( final File source, final File target ) { if ( source == null ) { why = "null source"; return false; } if ( target == null ) { why = "null target"; return false; } if ( target.exists() ) { if ( !target.canWrite() ) { // canWrite true implies file exists. why = "unwriteable target"; return false; } try { // O P E N FileInputStream is = new FileInputStream( source ); FileOutputStream os = new FileOutputStream( target, true/* append */ ); // C O P Y _ S O U R C E _ T O _ T A R G E T long fileLength = source.length(); // C L O S E // handled by inner copy return copy( is, os, fileLength, true/* close target too */ ); } catch ( IOException e ) { why = e.getMessage(); return false; } } else // target does not yet exist, just copy. { return copy( source, target ); } } // end append /** * Copy a file to an OutputStream * * @param source file to copy on local hard disk. * @param target OutputStream to copy the file to. * @param closeTarget true if the target OutputStream should be closed when we are done. false if the target * OutputStream should be left open for further output when done. * * @return true if the copy was successful. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion", "UnusedDeclaration" } ) public boolean copy( final File source, final OutputStream target, final boolean closeTarget ) { if ( source == null ) { why = "null source"; return false; } if ( target == null ) { why = "null target"; return false; } try { // O P E N FileInputStream is = new FileInputStream( source ); // C O P Y _ S O U R C E _ T O _ T A R G E T long fileLength = source.length(); // C L O S E of both both source and target handled by inner copy return copy( is, target, fileLength, closeTarget ); } catch ( IOException e ) { why = e.getMessage(); return false; } } // end copy /** * Copy an InputStream to an OutputStream when you know the length in advance. * * @param source InputStream, left closed. * @param target OutputStream, left closed. * @param length how many bytes to copy, -1 if you don't know. * @param closeTarget true if you want the target stream closed when done. false if you want to the target * stream left open for further output. * * @return true if the copy was successful. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion" } ) public boolean copy( final InputStream source, final OutputStream target, final long length, final boolean closeTarget ) { if ( length <= 0 ) { // indeterminate length return copy( source, target, closeTarget ); } if ( source == null ) { why = "null source"; return false; } if ( target == null ) { why = "null target"; return false; } try { // R E A D / W R I T E by chunks // we know length > 0 int chunkSize = ( int ) Math.min( buffSize, length ); long chunks = length / chunkSize; int lastChunkSize = ( int ) ( length % chunkSize ); // code will work even when lastChunkSize = 0 or chunks = 0; byte[] ba = new byte[ chunkSize ]; for ( long i = 0; i < chunks; i++ ) { int bytesRead = Read.readBytesBlocking( source, ba, 0, chunkSize ); if ( bytesRead != chunkSize ) { throw new IOException( "bug: short chunk" ); } target.write( ba ); } // end for // R E A D / W R I T E last chunk, if any if ( lastChunkSize > 0 ) { int bytesRead = Read.readBytesBlocking( source, ba, 0 /* offset in ba */, lastChunkSize ); if ( bytesRead != lastChunkSize ) { throw new IOException( "bug: short chunk" ); } target.write( ba, 0/* offset */, lastChunkSize/* length */ ); } // end if // C L O S E source.close(); if ( closeTarget ) { target.close(); } return true; } catch ( IOException e ) { why = e.getMessage(); return false; } } // end copy /** * Copy a file from a remote URL to a local file on hard disk. To use this method with a file that requires * userid/password to access it, see http://mindprod.com/jgloss/authentication.html * * @param source remote URL to copy. e.g. new URL("http://www.billabong.com:80/songs/lyrics.txt") * works with http:, https:, file: and possibly others. * @param target new file to be created on local hard disk. * @param sni true if should use server name identification * * @return true if the copy was successful. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion" } ) public boolean download( final URL source, final File target, final boolean sni ) { if ( source == null ) { why = "null source"; return false; } if ( target == null ) { why = "null target"; return false; } try { if ( sni ) { System.setProperty( "jsse.enableSNIExtension", "true" ); } else { System.setProperty( "jsse.enableSNIExtension", "false" ); } // Not actually connecting yet, just getting connection object, // urlc will contain subclasses of URLConnection like: // http: HttpURLConnection // https: HttpsURLConnectionImpl // file: FileURLConnection - URLConnection urlc = source.openConnection(); if ( urlc == null ) { throw new IOException( "Unable to make a connection." ); } else { if ( DEBUGGING ) { // get simple name of class without the package String s = urlc.getClass().getName(); // manually chop off all past the last dot. int lastDot = s.lastIndexOf( '.' ); if ( lastDot >= 0 ) { s = s.substring( lastDot + 1 ); } out.println( " Connecting to " + source.toString() + " with " + s ); } } urlc.setAllowUserInteraction( false ); urlc.setDoInput( true ); urlc.setDoOutput( false ); urlc.setUseCaches( false ); setStandardProperties( urlc ); urlc.connect(); // ignored if already connected. if ( DEBUGGING ) { final HttpURLConnection urlch = ( HttpURLConnection ) urlc; final int responseCode = urlch.getResponseCode(); final String responseMessage = urlch.getResponseMessage(); out.println( " Response: " + responseCode + " " + responseMessage ); } long length = urlc.getContentLength();// -1 if not available // O P E N _ S O U R C E, raw byte stream InputStream is = urlc.getInputStream(); // O P E N _ T A R G E T FileOutputStream os = new FileOutputStream( target ); // C O P Y _ S O U R C E _ T O _ T A R G E T // C L O S E of InputStream handled by copy. There is no corresponding URLConnection.disconnect return copy( is, os, length, true/* close target */ ); } catch ( IOException e ) { why = e.getMessage(); return false; } } // end download }