import java.awt.Color; import java.awt.Container; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.security.PrivilegedActionException; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.swing.AbstractButton; import javax.swing.JApplet; import javax.swing.JFileChooser; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import netscape.javascript.JSObject; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; import com.jcraft.jsch.SftpProgressMonitor; import com.jcraft.jsch.UserInfo; /** * This class implements an applet capable of sending a file or set of files to a specific standard UNIX * SSH server residing on the host specified on the codebase of the applet. The send functionality is * controlled completely via JavaScript. The applet also provides a mechanism for using callback functions * to report the percentage of the file sent (in real-time), to provide a status message, and to report * file completion (success or failure). These may be set either by parameters to the applet, or via * JavaScript calls to the applet. * * Authentication (permission to send a file) is based on a security token that is retrieved by the external * page prior to the sendFile call. The token is passed into the sendFile call, which then contacts the * authentication webservice (again, on the same host as the applets codebase) and verifies that the * transfer is allowed. If allowed, the webservice gives the applet the username / password for the * SFTP session, and a directory to place the file in (presumably so that the webservice can process it * at a later point). When completed, the applet will send a notification to the webservice with the * filename and the transfer status. * * @author Brad Broerman * @version 1.5 **/ public class SFTPApplet extends JApplet { private static final long serialVersionUID = -8551435712605531486L; private String ftpHost; // The host to send the file to. Derived from codebase. private final int ftpPort=22; // The port to use for SFTP. private String ftpTempFolder = "/"; // Folder that we upload to. Set from the authorization service. private String ftpUserName = null; // User name for the FTP server. Set by the authorization service. private String ftpUserPass = null; // Password for the FTP server. Also set by the authorization service. private String currAccessCode = null; // Access code. Set by JavaScript before calling auth service, and read afterwards. private String lastFolderSelected = null; // The directory the user last selected files from. private ArrayList<String> listOfFilesSelected = null; // Tracks the files selected last time the UI was launched. Only these files may be sent. private String jsOvrPctCallBackMethod = null; // Overall percentage complete callback. private String jsPctCallBackMethod = null; // Current file percentage complete callback. private String jsSttCallBackMethod = null; // Status message callback. private String jsFileSentCallBackMthd = null; // Current file sent/complete callback. private String jsFinalCallBackMthd = null; // All files sent/complete callback. private String jsFileSelectCallBackMethod = null; // Files selected callback. private JSObject window = null; // JavaScript window object. private JSObject document = null; // JavaScript document object. private FileSelectThread fileSelectionThread = null; // Thread for file selection. private Thread ftpTransferThread = null; // Thread for FTP transfer. private Container content = null; // Content pane for JFileChooser Swing object. private Boolean returnCodeForTransfer = false; // Used in the FTP transfer ( Work-around for Java bug 6669818 ) /** * Called by the web container to set up the Applet instance. * Sets the look and feel, gets references to the web document, and gets the * initial parameters from the applet tag. * * @access Public * @param None. * @return None. */ public void init( ) { // // Set the SWING UI up for the file chooser. // try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { System.out.println("Error setting native 'Look And Feel': " + e); } content = getContentPane(); content.setBackground(Color.white); content.setLayout(new FlowLayout()); // // Dynamically register the JSSE provider (this is in case the base URL for the auth service is HTTPS). // java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); // // Set this property to use Sun's reference implementation of the HTTPS protocol (if needed). // System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol"); // // The window and document references will be used later for talking to the calling page. // window = JSObject.getWindow(this); document = (JSObject)window.getMember( "document" ); // // Get the host for the FTP. This MUST be the same host that the page is served from. // ftpHost = getDocumentBase().getHost(); // // Used by the container to make sure the FTPConnection class is available. // @SuppressWarnings("unused") String classArray[] = { "FTPConnection" }; // // Set the JavaScript callback function to report percentage complete (as a parameter). // jsPctCallBackMethod = this.getParameter("FilePercentCallBack"); jsOvrPctCallBackMethod = this.getParameter("PercentCallback"); // // Set the callback function for status messages (as a parameter). // jsSttCallBackMethod = this.getParameter("StatusCallback"); // // Set the callback function for file complete message. // jsFinalCallBackMthd = this.getParameter("FTPCompleteCallback"); jsFileSentCallBackMthd = this.getParameter("FileCompleteCallback"); // // Set the callback functino for the selected files (from the file chooser) // jsFileSelectCallBackMethod = this.getParameter("FileSelectCallback"); } /** * Set / Reset the JavaScript callback to report overall percentage complete. * * @access Public * @param String callBackFuncName - The name of the JavaScript callback function. * @return None. * @throws None. */ public void setPercentCallBack( String callBackFuncName ) { jsOvrPctCallBackMethod = callBackFuncName; } /** * Set / Reset the JavaScript callback to report percentage complete for the current file. * * @access Public * @param String callBackFuncName - The name of the JavaScript callback function. * @return None. * @throws None. */ public void setFilePercentCallBack( String callBackFuncName ) { jsPctCallBackMethod = callBackFuncName; } /** * Set / Reset the callback for status messages. * * @access Public * @param String callBackFuncName - The name of the JavaScript callback function. * @return None. * @throws None. */ public void setStatusCallBack( String callBackFuncName ) { jsSttCallBackMethod = callBackFuncName; } /** * Set the callback function for reporting the send status of the current file. * * @access Public * @param String callBackFuncName - The name of the JavaScript callback function. * @return None. * @throws None. */ public void setFileCompleteCallBack( String callBackFuncName ) { jsFileSentCallBackMthd = callBackFuncName; } /** * Set the callback function for reporting the send status for the full transaction. * * @access Public * @param String callBackFuncName - The name of the JavaScript callback function. * @return None. * @throws None. */ public void setFtpCompleteCallBack( String callBackFuncName ) { jsFinalCallBackMthd = callBackFuncName; } /** * Set the callback function to pass back the selected files (from the file chooser) * * @access Public * @param String callBackFuncName - The name of the JavaScript callback function. * @return None. * @throws None. */ public void setFileSelectCallBack( String callBackFuncName ) { jsFileSelectCallBackMethod = callBackFuncName; } /** * This function is used to read the new session security code after a send has been started. * this code will be used in the JavaScript code to validate all future interactions. * * @access Public * @param None. * @return String - the current access code (if set) * @throws None. */ public String getAccessCode( ) { return currAccessCode; } /** * Used to set the session security code. * * @access Public * @param String accessCode - The new session security code. * @return None. * @throws None. */ public void setAccessCode( String accessCode ) { currAccessCode = accessCode; } /** * Send one or more files to the server via SFTP. This method is called from JavaScript on the document. * * @access Public * @param String accessCode - The access code for the current transaction. * @param String fileList - Semicolon delimited list of files to send. * @return Boolean - True for success, False for error. * @throws None. */ public boolean sendFiles(final String accessCode, final String fileList) { returnCodeForTransfer = false; SwingUtilities.invokeLater(new Runnable() { public void run() { returnCodeForTransfer = sendFilesLater(accessCode, fileList); } }); return returnCodeForTransfer; } /** * Send one or more files to the server via SFTP. This method is called from JavaScript on the document. * The files are determined directly from the last run file selection. * * @access Public * @param String accessCode - The access code for the current transaction. * @return Boolean - True for success, False for error. * @throws None. */ public boolean sendFiles(final String accessCode) { returnCodeForTransfer = false; SwingUtilities.invokeLater(new Runnable() { public void run() { returnCodeForTransfer = sendFilesLater(accessCode, ""); } }); return returnCodeForTransfer; } /** * Send one or more files to the server via SFTP. This method is called from JavaScript on the document. * The files are determined directly from the last run file selection. * The access code is the internal code from the last applet issued transaction with the webservice. * * @access Public * @param None. * @return Boolean - True for success, False for error. * @throws None. */ public boolean sendFiles( ) { returnCodeForTransfer = false; SwingUtilities.invokeLater(new Runnable() { public void run() { returnCodeForTransfer = sendFilesLater("", ""); } }); return returnCodeForTransfer; } /** * This is a hack work-around for Java bug 6669818. Sun is not likely to fix this, * as they have closed the bug without an official work-around or a fix... merely a * confirmation of the bug. * * @access Private * @param String accessCode - The access code for the current transaction. * @param String fileList - Semicolon delimited list of files to send. * @return Boolean - True for success, False for error. * @throws None. */ private boolean sendFilesLater( String accessCode, String filesToSend ) { boolean returnCode = false; // // Multiple files may be passed in, as a semi-colon separated list, // as a single file, or as an empty string. // If fileList is blank or null, then use the list in listOfFilesSelected. // String[] fileList = new String[1];; if( null != filesToSend && filesToSend.indexOf(';') > 0) fileList = filesToSend.split(";"); else if( null != filesToSend && filesToSend.length() > 0 ) { fileList[0] = new String(filesToSend); } else { fileList = listOfFilesSelected.toArray( fileList ); } // // If the passed in accessCode is blank or null, use the internal one. // if( null == accessCode || accessCode.length() == 0 ) { accessCode = this.currAccessCode; } else { this.currAccessCode = accessCode; } // // Filter incoming fileList to those files in listOfFilesSelected. // fileList = intersection( fileList, listOfFilesSelected.toArray(new String[listOfFilesSelected.size()])); // // We can only send one set of files at a time, so make sure the send thread isn't in operation. // if( null == ftpTransferThread || false == ftpTransferThread.isAlive()) { // // Get authorization for this transfer. This will come from the authorization // server by passing the current valid access code. This is compared to the one // stored on the server for the current session. If they match, the transfer can // proceed. This call also sets the class variables for the FTP user name, password, // and temporary directory, as well as the new current code. // if( getAuthentication(accessCode, filesToSend)) { // Create our file selection thread ftpTransferThread = new SFTPThread( fileList ); // and let it start running ftpTransferThread.start(); // Indicate that we started successfully. returnCode = true; } else { // // Authentication with host server failed. // if( null != jsSttCallBackMethod) window.eval( jsSttCallBackMethod + "('Unable to authorize FTP transfer.');"); else System.err.println("SFTPApplet - Unable to authorize FTP transfer."); if( null != jsFinalCallBackMthd ) window.eval( jsFinalCallBackMthd + "( false );"); } } return returnCode; } /** * Open the file chooser in a separate thread, and let the users select one or more files. * The selected files will be sent back to JavaScript with the registered callback function. * * @access Public * @param boolean rememberLastDir - Start in the directory last selected. * @return boolean - True on success, False on error. */ public boolean getFiles( boolean rememberLastDir ) { boolean returnCode = false; // // Only one thread at a time. // if( null == fileSelectionThread || false == fileSelectionThread.isAlive()) { // Create our file selection thread if( rememberLastDir ) fileSelectionThread = new FileSelectThread(true,lastFolderSelected); else fileSelectionThread = new FileSelectThread(true); // and let it start running fileSelectionThread.start(); returnCode = true; } return returnCode; } /** * Open the file chooser in a separate thread, and let the users select only ONE file. * The selected file will be sent back to JavaScript with the registered callback function. * * @access Public * @param boolean rememberLastDir - Start in the directory last selected. * @return boolean - True on success, False on error. */ public boolean getSingleFile( boolean rememberLastDir ) { boolean returnCode = false; // // Only one thread at a time. // if( null == fileSelectionThread || false == fileSelectionThread.isAlive()) { // Create our file selection thread if( rememberLastDir ) fileSelectionThread = new FileSelectThread(false,lastFolderSelected); else fileSelectionThread = new FileSelectThread(false); // and let it start running fileSelectionThread.start(); returnCode = true; } return returnCode; } /** * Call a specific webservice (on the same host that the applet was served from) with specified message (post parameters) * This method wraps the actual webservice call with a doPrivileged call to get past Java Applet security issues. * * @param String serviceName - The webservice script to call. * @param String message - The post parameters to send to the service. * @return Document - an XML document containing the response of the webservice. * @throws Throwable - Due to the PrivilegedExceptionAction used, all exceptions are sent as a throwable */ @SuppressWarnings({ "unchecked", "rawtypes" }) private Document callWebService( String serviceName, String message ) throws Throwable { Document xmlReply = null; try { final String f_service = serviceName; final String f_parms = message; xmlReply = (Document)java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction() { public Object run() throws ServerException, SAXException, ParserConfigurationException, IOException { Document xmlReply = doCallWebService( f_service, f_parms ); return xmlReply; } } ); } catch ( PrivilegedActionException e ) { throw e.getCause(); } return xmlReply; } /** * Call a specific webservice (on the same host that the applet was served from) with specified message (post parameters) * This method performs the actual webservice call. * * @param String serviceName - The webservice script to call. * @param String message - The post parameters to send to the service. * @return Document - an XML document containing the response of the webservice. * @throws MalformedURLException, IOException, ServerException */ private Document doCallWebService( String serviceName, String message ) throws MalformedURLException, IOException, ServerException, SAXException, ParserConfigurationException { // // Create the URL object, and open the connection to the web service. // URL url = new URL( super.getCodeBase(), serviceName ); // // We know that it's either HTTP or HTTPS, so we can cast to the // HttpUrlConnection class here. This way, we can get the response // code directly. // HttpURLConnection con = (HttpURLConnection)url.openConnection(); con.setDoInput(true); con.setDoOutput(true); con.setUseCaches(false); // // Set the content length header, based on the message size. // con.setRequestProperty("CONTENT_LENGTH", "" + message.length() ); // actually not checked // // Copy the session cookies from the parent page, and include them on this request. // // con.setRequestProperty("Cookie", (String) document.getMember( "cookie" )); // // Prepare to write to the server. // OutputStream os = con.getOutputStream(); // // Send the request. // os.write(message.getBytes("UTF-8")); os.flush(); os.close(); // // Now, read the response! // // // Check the response header. Make sure we're getting a response in the 200 range. // if( con.getResponseCode() >= 300 || con.getResponseCode() < 200 ) { // // If not, throw an exception to notify the caller that there was a problem. // // throw new ServerException( con.getResponseMessage() ); } // // Now, get the response text. Convert it to a Document. // DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(con.getInputStream()); // // Return the response to the caller // return doc; } /** * Get a list of file filters from the ftpauthserver.php webservice on the server. * Side-effect of this call is the current internal accessCode is modififed. * * @access Private * @param String accessCode - current security access code. * @param boolean multiFiles - is the front-end asking to be able to select and send multiple files (true/false). * @return List<ExampleFileFilter> - The file filters to be used by the multi-file select widget. * @throws None */ private List<ExampleFileFilter> getValidFileTypes( String accessCode, boolean multiFiles ) { // // This method will call the file authorization webservice to get the list // of allowable file types. The response will be in the XML format: // <response> // <success> // <filter desc='name of filter' default='true'> // <ext> extension </ext> // <ext> extension </ext> // <ext> extension </ext> // </filter> // <filter desc='name of filter'> // <ext> extension </ext> // <ext> extension </ext> // <ext> extension </ext> // </filter> // <newauth> new code </newauth> // </success> // </response> // etc... try { // // Set the security key, and action (upload photo). These will be POST parameters. // String params = "key=" + URLEncoder.encode(accessCode, "UTF-8") + "&action=getvalidfileslist"; if(multiFiles) { params += "&multifile=true"; } // // Call the webservice. // Document xmlReply = callWebService( "ftpauthserver.php", params ); // // Now, if we get other responses (other than the 200 OK) we won't get what we're looking for // below anyway, and we'll get an authentication error... That's ok... The full error message // will be written to the Java Console log, so we can debug. // if( xmlReply.getElementsByTagName("response").getLength() > 0 ) { Element successNode = (Element) xmlReply.getElementsByTagName("success").item(0); if( null != successNode ) { List<ExampleFileFilter> returnList = new ArrayList<ExampleFileFilter>(); // Now, get the list of filters NodeList filterNodes = successNode.getElementsByTagName("filter"); for( int idx = 0; idx < filterNodes.getLength(); ++idx) { ExampleFileFilter newFilter = new ExampleFileFilter(); Element currFilter = (Element) filterNodes.item(idx); String desc = currFilter.getAttribute("desc"); if( desc == null || desc.length() == 0 ) { desc = ""; } newFilter.setDescription(desc); String def = currFilter.getAttribute("default"); if( def != null && def.length() > 0 && def.equalsIgnoreCase("true")) { newFilter.setDefault(true); } NodeList extensions = currFilter.getElementsByTagName("ext"); for( int extidx = 0; extidx < extensions.getLength(); ++extidx) { Element currext = (Element) extensions.item(extidx); newFilter.addExtension(currext.getTextContent()); } returnList.add(newFilter); } currAccessCode = successNode.getElementsByTagName("newauth").item(0).getTextContent(); return returnList; } else { // if no success nodes, then there has to be a failed node. Element failedNode = (Element) xmlReply.getElementsByTagName("failed").item(0); if( null != failedNode ) System.err.println("SFTPApplet::getValidFileTypes() - Failed getting filter list: " + failedNode.getTextContent()); } } else { System.err.println("SFTPApplet::getValidFileTypes() - Unknown response from action server."); } } catch(Throwable e) { System.err.println("SFTPApplet::getValidFileTypes() - Caught Exception: " + e.toString()); } return null; } /** * Request permission to send the listed files to the server. This calls the ftpauthserver.php and passes * the file list, and access code, and asks the webservice for permission to upload the files. * Side effects: IF successful, the internal state variables accessCode, ftpTempFolder, ftpUserName, ftpUserPass * are set. ftpTempFolder is the location to place the file, and ftpUserName and ftpUserPass are the credentials * for logging into the server. * * @access Private * @param String accessCode - The current security access code. * @param String fileList - A colon delimited list of the files to be sent. * @return boolean - true if the transfer is allowed, false if it is not. * @throws None */ private boolean getAuthentication( String accessCode, String fileList ) { // // This method will call a special method on the bbphotoadminaction.php // webservice to determine if the current user is authorized to upload a // file to the server. It will pass the session ID and the current // code string (used to validate that the connection wasn't hacked). If the // webservice determines we can send, it will send back the FTP credentials and // a new security code. These will be stored in the appropriate class variables. // try { // // Set the security key, and action (upload photo). These will be POST parameters. // String params = "key="+URLEncoder.encode(accessCode, "UTF-8")+"&action=uploadPhoto" + "&filelist="+URLEncoder.encode(fileList, "UTF-8"); // // Call the webservice. // Document xmlReply = callWebService( "ftpauthserver.php", params ); // // Now, if we get other responses (other than the 200 OK) we won't get what we're looking for // below anyway, and we'll get an authentication error... That's ok... The full error message // will be written to the Java Console log, so we can debug. // if( xmlReply.getElementsByTagName("response").getLength() > 0 ) { // // We should get back an XML document with the // following parameters: // // <response> // <success> // <dir> directory_name </dir> // <usr> ssh_user_name </usr> // <pass> ssh_password </pass> // <newauth> new code </newauth> // </success> // </response> // // The directory name, username, and password will bet set in instance variables for // later use. // // A failure would change the <success> with <failed> and a reason. // Element successNode = (Element) xmlReply.getElementsByTagName("success").item(0); if( null != successNode ) { // // Now, get each of the required parameters... // ftpTempFolder = successNode.getElementsByTagName("dir").item(0).getTextContent(); ftpUserName = successNode.getElementsByTagName("usr").item(0).getTextContent(); ftpUserPass = successNode.getElementsByTagName("pass").item(0).getTextContent(); currAccessCode = successNode.getElementsByTagName("newauth").item(0).getTextContent(); return true; } else { // if no success nodes, then there has to be a failed node. Element failedNode = (Element) xmlReply.getElementsByTagName("failed").item(0); if( null != failedNode ) System.err.println("Authorization failed: " + failedNode.getTextContent()); } } else { System.err.println("Unknown response from action server."); } } catch(Throwable e) { System.err.println("SFTPApplet::getAuthentication() - Caught Exception: " + e.toString()); } return false; } /** * Notify the server that the file transfer has completed. It indicates a number of files sent, and * the list of files. * * @param int numFilesSent - The number of files successfully sent. * @param String fileList - A colon delimited list of files sent. * @return boolean - true on success, false on failure. * @throws None */ private boolean sendFileComplete(int numFilesSent, String fileList ) { // // This method will call a special method on the ftpauthserver.php // webservice to tell the application that the file has been successfully // sent, and can now be processed by the program. // try { String params = "key="+URLEncoder.encode(currAccessCode, "UTF-8")+"&action=photoUploadDone&numFilesSent="+ Integer.toString(numFilesSent)+"&filelist="+URLEncoder.encode(fileList, "UTF-8"); // // Call the webservice. // Document xmlReply = callWebService( "ftpauthserver.php", params ); // // We should get back an XML document with the // following parameters: // // <response> // <success> // <newauth> new code </newauth> // </success> // </response> // // A failure would change the <success> with <failed> and a reason. // if( xmlReply.getElementsByTagName("response").getLength() > 0 ) { Element successNode = (Element) xmlReply.getElementsByTagName("success").item(0); if( null != successNode ) { currAccessCode = successNode.getElementsByTagName("newauth").item(0).getTextContent(); return true; } else { // if no success nodes, then there has to be a failed node. Element failedNode = (Element) xmlReply.getElementsByTagName("failed").item(0); if( null != failedNode ) System.err.println("Server error responding to sent notification."); } } else { System.err.println("Unknown response from action server."); } } catch(Throwable e) { System.err.println("FTPApplet::sendFileComplete() - Caught Exception: " + e.toString()); } return false; } /** * Required by JSCH library for connecting to a SSH server. Specifies the username, password, and scripts * interactions necessary for the completion of the transfer (like accepting the remote server key). * * @author Brad Broerman */ public static class MyUserInfo implements UserInfo { private String password; @SuppressWarnings("unused") private String username; public MyUserInfo( String username, String password ) { this.username = username; this.password = password; } public String getPassphrase() { return null; } public String getPassword() { return password; } public boolean promptPassword(String message) { return true; } public boolean promptPassphrase(String message) { return true; } public boolean promptYesNo(String message) { System.out.println("Was Prompted: \"" + message + "\". Returning Yes."); return true; } public void showMessage(String message) { System.out.println("Received a message:"); System.out.println(message); return; } } /** * This class is used to track the percentage complete, and call the JavaScript callbacks. * It will track both the percentage for the current file, as well as the overall percentage. * * The constructor sets the number of files that are going to be sent, as well as * the total file size. * */ public static class MyProgressMonitor implements SftpProgressMonitor { public interface CallBack { void update(float filePct, float ttlPct); } CallBack callb = null; int numFiles = 0; long fileCount = 0L; long fileMax = 0L; long totalMax = 0L; long totalCount = 0L; public MyProgressMonitor( CallBack inCallb, int numFiles, long totalFileBytes) { this.callb = inCallb; this.totalMax = totalFileBytes; this.numFiles = numFiles; } public void init(int op, String src, String dest, long max) { this.fileMax = max; } public boolean count(long count) { this.fileCount += count; this.totalCount += count; float filePercentage = (float)this.fileCount * 100.0F / (float)this.fileMax; float totalPercentage = (float)this.totalCount * 100.0F / (float)this.totalMax; this.callb.update(filePercentage,totalPercentage); return true; } public void end() { long delta = this.fileMax - this.fileCount; this.fileCount = this.fileMax; this.totalCount += delta; } } public class SFTPThread extends Thread { String[] filesToSend = null; // Construct the transfer to send one or more files. public SFTPThread( String[] inFileList ) { // yeah, it's a shallow copy, but the contents aren't mutable. filesToSend = inFileList.clone(); } // // This method sends the file in a thread, reporting status and percentage complete if // callbacks are set. The currFileInProgress boolean flag indicates whether a transfer // is currently in progress or not. It is reset when the method is complete. // @SuppressWarnings({ "unchecked", "rawtypes" }) public void run() { try { // // Create the SSH connection object. // java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction() { public Object run() throws Exception { Session session = null; Channel channel = null; ChannelSftp c = null; StringBuffer filesSent = new StringBuffer(); try { JSch jsch=new JSch(); // Open the SSH session to the host. session=jsch.getSession(ftpUserName, ftpHost, ftpPort); // Password will be provided by teh MyUserInfo class. UserInfo ui=new MyUserInfo(ftpUserName, ftpUserPass); session.setUserInfo(ui); if( null != jsSttCallBackMethod) window.eval( jsSttCallBackMethod + "('Connecting.');"); // Connect the session. session.connect(); } catch(JSchException e) { if( null != jsSttCallBackMethod) window.eval( jsSttCallBackMethod + "('Error connecting to server: " + e.getMessage() + "');"); throw e; } try { // Here we're going to set up an SFTP channel, and connect it to the session. channel=session.openChannel("sftp"); channel.connect(); c=(ChannelSftp)channel; } catch(JSchException e) { if( null != jsSttCallBackMethod) window.eval( jsSttCallBackMethod + "('Error connecting SFTP channel: " + e.getMessage() + "');"); throw e; } // // Get the count of the number of files to send. // int nbrFilesToSend = filesToSend.length; int nbrFilesSent = 0; // // Get the total size (in bytes) of all the files. // long totalFileSize = 0L; for( int idx = 0; idx < nbrFilesToSend; ++idx ) { String currFile = filesToSend[idx]; File tmpFile = new File( currFile ); totalFileSize += tmpFile.length(); } // // Set the callback for the percentage complete notification to the // JavaScript (if the callback method name is set). // MyProgressMonitor pm = new MyProgressMonitor( new MyProgressMonitor.CallBack() { public void update(float filePercent, float totalPercent ) { if( null != jsPctCallBackMethod ) { window.eval( jsPctCallBackMethod + "(" + filePercent + ");"); } if( null != jsOvrPctCallBackMethod ) { window.eval( jsOvrPctCallBackMethod + "(" + totalPercent + ");"); } }}, nbrFilesToSend, totalFileSize); // // Now, let's send the files... one by one. // try { for( int idx = 0; idx < nbrFilesToSend; ++idx ) { String currFile = filesToSend[idx]; System.err.println("Sending current file: " + currFile ); System.err.println("Temp Folder: " + ftpTempFolder ); if( null != jsSttCallBackMethod) { String tmpStr = currFile.replaceAll("\\\\","\\\\\\\\"); window.eval( jsSttCallBackMethod + "('Sending "+ tmpStr +". ("+(idx+1)+" of "+nbrFilesToSend+"');"); } c.put(currFile, ftpTempFolder, pm, ChannelSftp.OVERWRITE); if( null != jsFileSentCallBackMthd) window.eval( jsFileSentCallBackMthd + "(true,'" + currFile + "');"); filesSent.append(currFile); filesSent.append(";"); nbrFilesSent++; } } catch( SftpException e) { if( null != jsSttCallBackMethod) { window.eval( jsSttCallBackMethod + "('Error sending files: " + e.getMessage() + "');"); } try { sendFileComplete(nbrFilesSent, filesSent.toString()); } catch( Throwable t ) { System.err.print("Caught exception sending transfer status to webservice: " + t.getMessage() ); t.printStackTrace(); } throw e; } try { sendFileComplete(nbrFilesSent, filesSent.toString()); } catch( Throwable e ) { System.err.print("Caught exception sending transfer status to webservice: " + e.getMessage() ); e.printStackTrace(); } return new Boolean(true); } } ); // // Update the client with the status. // if( null != jsSttCallBackMethod) window.eval( jsSttCallBackMethod + "('Complete.');"); // // Inform the JavaScript that we're successfully done. // if( null != jsFinalCallBackMthd ) window.eval( jsFinalCallBackMthd + "( true );"); } catch( Exception e) { System.err.println("SFTPApplet::sendFile() - Caught Exception: " + e.toString()); if( null != jsFinalCallBackMthd ) window.eval( jsFinalCallBackMthd + "( false );"); } } } // // This is the thread for the JFileChooser. // private class FileSelectThread extends Thread { boolean showMultiFiles = true; String startingFolder = ""; @SuppressWarnings("unused") public FileSelectThread() { super(); } public FileSelectThread( boolean selectMultipleFiles ) { super(); showMultiFiles = selectMultipleFiles; } public FileSelectThread( boolean selectMultipleFiles, String startFolder ) { super(); showMultiFiles = selectMultipleFiles; startingFolder = startFolder; } @SuppressWarnings({ "unchecked", "rawtypes" }) public void run() { String fileList = (String)java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { public Object run() { JFileChooser chooser = null; if( null != startingFolder ) { chooser = new JFileChooser(new File(startingFolder)); } else { chooser = new JFileChooser(); } chooser.setFileView(new ImageFileView()); chooser.setMultiSelectionEnabled(showMultiFiles); List<ExampleFileFilter>filters = getValidFileTypes( currAccessCode, showMultiFiles ); if( null != filters ) { Iterator<ExampleFileFilter> iter = filters.iterator(); while(iter.hasNext()) { ExampleFileFilter nextFilter = iter.next(); chooser.addChoosableFileFilter(nextFilter); if( nextFilter.isDefault() ) { chooser.setFileFilter(nextFilter); } } } AbstractButton detailbutton = SwingUtils.getDescendantOfType(AbstractButton.class, chooser, "Icon", UIManager.getIcon("FileChooser.detailsViewIcon")); AbstractButton listbutton = SwingUtils.getDescendantOfType(AbstractButton.class, chooser, "Icon", UIManager.getIcon("FileChooser.listViewIcon")); listbutton.addActionListener( new ListActionListener(chooser) ); detailbutton.addActionListener( new DetailActionListener(chooser) ); if( null == content ) System.err.println("Content Pane is null"); int returnVal = chooser.showOpenDialog(content); if(returnVal == JFileChooser.APPROVE_OPTION) { StringBuffer fileList = new StringBuffer(); File[] files = chooser.getSelectedFiles(); listOfFilesSelected = new ArrayList<String>(); for( int i = 0; i < files.length; ++i ) { String tmpStr = files[i].getAbsolutePath(); listOfFilesSelected.add( new String(tmpStr) ); tmpStr = tmpStr.replaceAll("\\\\","\\\\\\\\"); fileList.append(tmpStr); fileList.append(";"); } if( files.length > 0) { lastFolderSelected = files[0].getParent(); } return new String(fileList.toString()); } return new String(""); } final class DetailActionListener implements ActionListener { JFileChooser fc; public DetailActionListener(JFileChooser fc) { this.fc = fc; } public void actionPerformed(ActionEvent e) { fc.setFileView(null); } } final class ListActionListener implements ActionListener { JFileChooser fc; public ListActionListener(JFileChooser fc) { this.fc = fc; } public void actionPerformed(ActionEvent e) { fc.setFileView(new ImageFileView()); } } } ); try { if( null != jsFileSelectCallBackMethod) window.eval( jsFileSelectCallBackMethod + "('" + fileList + "');"); } catch( Exception e ) { System.out.println("Caught Exception: " + e.toString() ); } } } // This exception will be thrown when the webserver responds with an error HTTP response code. class ServerException extends Exception { private static final long serialVersionUID = 2156731186517403745L; public ServerException( String message ) { super(message); } } @SuppressWarnings("rawtypes") public static String join(AbstractCollection s, String delimiter) { StringBuffer buffer = new StringBuffer(); Iterator iter = s.iterator(); if (iter.hasNext()) { buffer.append(iter.next()); while (iter.hasNext()) { buffer.append(delimiter); buffer.append(iter.next()); } } return buffer.toString(); } public static String[] intersection(String[] first, String[] second) { // initialize a return set for intersections Set<String> intsIntersect = new HashSet<String>(); // load first array to a hash HashSet<String> array1ToHash = new HashSet<String>(); for (int i = 0; i < first.length; i++) { array1ToHash.add(first[i]); } // check second array for matches within the hash for (int i = 0; i < second.length; i++) { if (array1ToHash.contains(second[i])) { // add to the intersect array intsIntersect.add(second[i]); } } return intsIntersect.toArray(new String[intsIntersect.size()]); } }