Download servlet V1.54 / was: Re: download servlet

Debraj Das wrote:

> I've been using the following code (inspired by Roland Mainz's file
> transfer servlet code).
> I can recieve the file with no problems, but the file does not
> retain it's original name.
>
> in the example I have tried to send the file notepad.exe, but when
> my browser prompts me to save the file, the original name
> notepad.exe is not present.  I am prompted to download
> TestFileXferServlet (i.e. the name of my servlet).  I can download
> the file, rename it to notepad.exe (manually) and then run the file.
>
> How can I make it so that I retain the original file name during
> download?
>
>         public void doPost(HttpServletRequest req,
> HttpServletResponse res)throws javax.servlet.ServletException
>         {try{
>          System.out.println("sending file1.txt");
>          FileInputStream in = new
> FileInputStream("c:\\winnt\\notepad.exe");
>
>
> res.setContentType("application/x-www-form-urlencoded");

Uhhg... I don't know whether the idea with x-www-form-urlencoded is a
good one...

I've attached the current source of my SnoopServlet which runs here
for a long time without such problems (except in Win95... but an
update to a newer browser version should fix this problem...).
Note that I'll update this beast in the near future by a JSP-driven
engine (or something which uses a XML-based config) to get rid of
several problems (like that huge configs can be a pain to manage. The
biggest config here has 474 entries. Ouch...).

----

Bye,
Roland

--
  __ .  . __
 (o.\ \/ /.o) Roland.Mainz@informatik.med.uni-giessen.de
  \__\/\/__/  gisburn@informatik.med.uni-giessen.de
  /O /==\ O\  MPEG specialist, C&&JAVA&&Sun&&Unix programmer
 (;O/ \/ \O;) TEL +49 641 99-13193 FAX +49 641 99-41359
package de.gis.www.servlets.download;

import java.io.*;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;


/**
 * download area servlet
 *
 * @version 	1.54
 * @author 	Roland Mainz
 */
public
class DownloadServlet extends HttpServlet 
{
    // Class describing a single entry in out "download"-database 
    class DownloadItem
    {
        String reference;      // "reference="     config-file internal reference 
        String description;    // "descr"=         small, one-line description    
        String label;          // "label="         label, e.g. link text          
        String filename;       // "file="          path/filename on local disk    
        String filepart;       //                  filename-only part of filename 
        String contenttype;    // "contenttype="   mime-type of file              
        String user;           // "user="          if given, only include download item if session user is the same 
        long   filesize;       //                  file size in bytes             
        
        DownloadItem()
        {
            // defaults 
            description = "n/a";
            contenttype = "application/octet-stream";
            user        = null;
        }
        
        
        // check wheter the user is allowed to download this item
        boolean checkUser( String requestUser )
        {
            // this download item has no user-access restriction ?
            if( user == null )
              return( true );                       
                         
            if( requestUser == null )
            {
              // download item has user access restriction, but we did not get any user info (anonymous access
              return( false );
            }  
              
            return( user.equalsIgnoreCase( requestUser ) );  
        }
        
        
        boolean matchReference( String match )
        {
            return( reference.compareTo( match ) == 0 );
        }
    }


    // Class describing a section in the sections database    
    class SectionItem extends Vector
    {
        String title;
        
        
        SectionItem()
        {
            super();
        }
        
        
        boolean hasEntriesForUser( DownloadServlet parent, String requestUser )
        {                          
            for( int i = 0 ; i < size() ; i++ )
            {
              DownloadItem di = parent.findDownLoadItemByRef( (String)get( i ) );
              
              if( di == null ) continue;
              
              if( di.checkUser( requestUser ) )
                return( true ); 
            }
            
            return( false );
        }
    }
    
    
    // Global vars (set up by init()) 
    String configfile_name         = null;
    long   configfile_last_scanned = 0L;
    File   configfile              = null;
    
    Vector downloadable_stuff    = new Vector();
    Vector sections              = new Vector(); // also used to syncronize threads during updates... 

    
    public void init( ServletConfig config ) throws ServletException
    {
        // this MUST be called first 
        super.init( config );
    
        d( "## init " );
        
        // get configuration parameters 
        configfile_name = config.getInitParameter( "CONFIGFILE" );             
        if( configfile_name == null ) throw new ServletException( "no CONFIGFILE parameter" );
                
        synchronized( sections )
        {
          buildIndex();
        }
    }
    
    
    private void updateIndex() throws ServletException
    {
        synchronized( sections )
        {
          boolean update_required;
          
          update_required = (configfile != null)?(configfile_last_scanned < configfile.lastModified()):(true);
        
          // update required ? 
          if( update_required )
          {
            d( "# updating index..." );
            
            // throw the old rubbish away... 
            downloadable_stuff.removeAllElements();
            sections.removeAllElements();
            
            // .. and build new info 
            buildIndex();
          }
        }
    }
    
    
    private void buildIndex() throws ServletException    
    {    
        try
        {                 
          // Note: We're silently loosing here the File object reference from any "old" config file... 
          configfile = new File( configfile_name );
        
          LineNumberReader in = new LineNumberReader( new FileReader( configfile ) );
          
          String s;
                             
          // loop until end-of-header... 
          while( (s = in.readLine()) != null )
          {
            // ignore comments 
            if( s.startsWith( "#" ) || s.startsWith( ";" ) || s.startsWith( "//" ) || (s.length() < 2) ) continue;
            
            // d( "processing line '" + s + "'" );
          
            StringTokenizer st = new StringTokenizer( s, ";" );
            
            try
            {
              String type = st.nextToken();              
              if( type == null ) throw new IllegalArgumentException( "bad token (type identifier) in config file" );
              
              // is this a section ? 
              if( type.equals( "section" ) )
              {                
                SectionItem si = new SectionItem();
                
                si.title = st.nextToken();
                if( si.title == null ) throw new IllegalArgumentException( "bad token (title expected) in config file" );
                
                while( st.hasMoreElements() )
                {
                  si.add( st.nextToken() );
                }
                
                sections.add( si );
              }              
              // is this a file to download ? 
              else if( type.equals( "file" ) )
              {                
                DownloadItem di = new DownloadItem();              
                
                while( st.hasMoreElements() )
                {
                  String element = st.nextToken();
                  if( element == null ) throw new IllegalArgumentException( "bad token (element expected)" );
                                                 
                  if( element.startsWith( "ref="         ) ) di.reference   = element.substring(  4 );
                  if( element.startsWith( "label="       ) ) di.label       = element.substring(  6 );
                  if( element.startsWith( "descr="       ) ) di.description = element.substring(  6 );
                  if( element.startsWith( "file="        ) ) di.filename    = element.substring(  5 );
                  if( element.startsWith( "contenttype=" ) ) di.contenttype = element.substring( 12 );
                  if( element.startsWith( "user="        ) ) di.user        = element.substring(  5 );
                }
                
                // d( "got: '" + di.label + "','" + di.filename + "','" + di.contenttype + "'" );
            
                // asserts, defaults, 1st level 
                if( di.filename == null          ) throw new IllegalArgumentException( "missing filename"  );
               
                if( di.filename.length()     < 2 ) throw new IllegalArgumentException( "filename to small" );
                if( di.contenttype.length()  < 2 ) throw new IllegalArgumentException( "??? content type"  );
                          
                // asserts, 2nd level 
                File downloadfile = new File( di.filename );
                
                di.filepart = downloadfile.getName();
                di.filesize = downloadfile.length();
            
                if( !downloadfile.exists() )     throw new IllegalArgumentException( "file does not exsist" );
                if( !downloadfile.isAbsolute() ) throw new IllegalArgumentException( "cannot export non-files with non-absolute filenames" );
                
                // defaults 
                if( di.reference == null ) di.reference = di.filepart;
                if( di.label     == null ) di.label     = di.filepart;
                                      
                downloadable_stuff.add( di );                
              }  
              else
              {
                throw new IllegalArgumentException( "line does not start with 'file' or 'section'" );
              }
            }
            catch( IllegalArgumentException exc )
            {
              d( "line " + in.getLineNumber() + " {" + s + "} rejected:" + exc );
            }                       
          }
          
          /* Seems to be a successfull index build. 
           * configfile_last_scanned is updated here to mark that the data loaded in this object are in sync with the configfile...
           */
          configfile_last_scanned = configfile.lastModified();
          d( "## ready" );
        }
        catch( Exception exc )
        {
          d( "init exception: " + exc );
          exc.printStackTrace();
          
          throw new ServletException( "buildIndex failed" );
        }    
    }
    
    
    public void destroy()
    {
        d( "## destroy" );
    }
    
    
    // debugging output 
    static private void d( String s )
    {
        System.err.println( "Download[" + Thread.currentThread().getName() + "]: " + s );
    } 


    public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException
    {
        d( "access from " + req.getRemoteUser() + "@" + req.getRemoteHost() + " (" + req.getRemoteAddr() + ")" );
        
        // debugging only (my debugging offline message)
        if( 1 != 1 )
        {
          PrintWriter out;  

	  res.setContentType( "text/html" );
	  out = res.getWriter();
	  
	  out.println( "<html><body><h1>Download area offline due internal problems</h1></body></html>" );
	  out.close();
	  
	  return;
        }

        // no article requested ? - Then send the index page... 
        if( (req.getPathInfo() == null)     ||
            req.getPathInfo().equals( "/" ) ||
            req.getPathInfo().startsWith( "#" ) )
        { 
          // check for updates... 
          updateIndex();
          
          doGetIndex( req, res ); 
        }
        else
        {
          doGetFile( req, res );
        }
    }      


    private void doGetIndex( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException
    {    
        String requestURI = req.getRequestURI();
        if( !requestURI.endsWith( "/" ) )
        {
          // the main page should act as a "directory", therefore we want a '/' at the end !
          res.sendRedirect( new String( javax.servlet.http.HttpUtils.getRequestURL( req ) + "/" ) );
          return;
        } 
        
        String requestUser = req.getRemoteUser();
        
	PrintWriter out;

	res.setContentType( "text/html" );
	out = res.getWriter();

	out.println( "<html>" );
	out.println( "<head>" );
	  out.println( "<title>Download Servlet output</title>" );   
	  out.println( "<meta name=\"Author\" content=\"Roland Mainz\">" );
          out.println( "<meta name=\"GENERATOR\" content=\"DownloadServlet/1.54\">" );
        out.println( "</head>" );
	
	// begin body and set page colors 
	out.println( "<body text=\"#000000\" bgcolor=\"#FFFFFF\" link=\"#0000EE\" vlink=\"#511A8B\" alink=\"#FF0000\">" );

        out.println( "<h1>Downloadable stuff...</h1>");
        out.println( "This page contains some usefull stuff used within our institute.<br>" );
        
        if( requestUser == null )
        {
          out.println( "We also have the <a href=\"/download_imi/\">same download area password protected</a> with more internal stuff.<br>" +
                       "Please be patient with the page layout; it is managed and updated automatically by our \"Download servlet\" daemon.<br>" );
        } 
        
        out.println( "<br>" );
        out.flush();
        
        // sync with any update in progress on another thread... 
        synchronized( sections )
        {  
          printJumpLine( out, requestUser );            

          out.println( "<table border width=\"98%\">" );
        
          out.println( "<tr><td><font size=\"+1\"><strong>Description</strong></font></td>" + 
                           "<td><font size=\"+1\"><strong>File</strong></font></td>"        + 
                           "<td><font size=\"+1\"><strong>Size</strong></font></td></tr>" );
        
        
	  // scan our table if sections 
          for( int i = 0 ; i < sections.size() ; i++ )
          {
            SectionItem si = (SectionItem)sections.get( i ); 
            
            // check whether the title line of this section has been written or not
            // this is used to avoid that a title is written for an empty section (maybe all items are inacessible)
            boolean wroteTitle = false;
          
            for( int j = 0 ; j < si.size() ; j++ )
            {
              String ref = (String)si.get( j );
            
              DownloadItem di = findDownLoadItemByRef( ref );           
            
              if( di != null )
              {                
                if( !di.checkUser( requestUser ) )
                  continue;
                  
                if( !wroteTitle )
                {
                  // section title and HTML target 
                  out.println( "<tr><td><strong><a name=\"" + java.net.URLEncoder.encode( si.title ) + "\">" + si.title + "</a></strong></td></tr>" );
                  wroteTitle = true;
                }  
                
                out.println( "<tr>" );
              
                  out.print( "  <td bgcolor=\"#F0F0F0\">" + di.description + "</td>" );
                  out.print( "  <td bgcolor=\"#FFFF00\">" + "<a href=\"" + requestURI + java.net.URLEncoder.encode( di.filepart ) + "\">" + di.label + "</a>" + "</td>" );
                  out.print( "  <td bgcolor=\"#C0C0C0\">" + di.filesize + " bytes" + "</td>" );
  
                out.println( "</tr>" );
              }
              else
              {
                d( "section item not found: " + si.title + "," + ref );
              }
            }         
          }
        
          out.println( "</table><br>" );
        }
        
        out.flush(); // give the browser all info to build the table...
        
        printJumpLine( out, requestUser );
        
        // print our footer... (got from the MailinglistServlet.class) 
        // this is now a "strip-down" of the XML footer:
        // simply copy-paste, removing all "html:" namespace identifies from the tags and removeing the </br> tags
        out.println( "  <br><p/><hr/><p/>" );
        out.println( "  <a href=\"http://www.w3.org/jigsaw/\"><img src=\"/icons/jigpower\" alt=\"Powered by Jigsaw [2.0.4]webserver.\" border=\"0\" align=\"abscenter\"></img></a>" );
        out.println( "  <br><font size=\"-1\">Written by <a href=\"mailto:jigsaw@informatik.med.uni-giessen.de\">Roland Mainz, IMI</a></font><br><font size=\"-1\">Last updated 04.04.2000</font><br>" );        
        
	out.println("</body></html>");
	
	out.close();
    }


    private void printJumpLine( PrintWriter out, String requestUser )
    {
        // scan our table if sections and build a small index line 
        if( sections.size() > 1 )
        {
          out.print( "<font size=\"-2\">[ " );
	    
	  int num = 0;
          for( int i = 0 ; i < sections.size() ; i++ )
          {
            SectionItem si = (SectionItem)sections.get( i ); 
              
            if( !si.hasEntriesForUser( this, requestUser ) )
              continue;
            
            if( num++ > 0 ) out.print( " | " );
            
            out.print( "<a href=\"#" + java.net.URLEncoder.encode( si.title ) + "\">" + si.title + "</a>" );
          }
        
	  out.println( " ]</font><br><br>" );          
        }
    }
    
    
    private DownloadItem findDownLoadItemByFilePart( String match )
    {
	// scan our table if "indexed" downloadable files, if we got a match... 
        for( int i = 0 ; i < downloadable_stuff.size() ; i++ )
        {
          DownloadItem di = (DownloadItem)downloadable_stuff.get( i ); 
                    
          // match ? 
          if( di.filepart.compareTo( match ) == 0 )
          {
            return( di );  
          }
        }
        
        return( null );
    }

    
    private DownloadItem findDownLoadItemByRef( String match )
    {
	// scan our table if "indexed" downloadable files, if we got a match... 
        for( int i = 0 ; i < downloadable_stuff.size() ; i++ )
        {
          DownloadItem di = (DownloadItem)downloadable_stuff.get( i ); 
                    
          // match reference ? 
          if( di.matchReference( match ) )
          {
            return( di );
          }
        }
        
        return( null );
    }
    
    
    // number of concurrent downloads
    static int maxUsers = 6;
    
    // start a download
    synchronized protected boolean startNewDownload()
    {
        if( maxUsers > 0 )
        {
          maxUsers--;
          return( true );
        }
        else
        {
          return( false );
        }
    }
    
    
    // download has been finished
    synchronized protected void finishDownload()
    {
        maxUsers++;
    }
    
    
    private void doGetFile( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException
    {    	
	DownloadItem di          = null;
        String       path        = req.getPathInfo().substring( 1 ); // get path, and get rid if it's leading '/'
        String       requestUser = req.getRemoteUser();
        
        // note that jigsaw does the proper URL decoding seems...
        d( "file requested: '" + path + "'" );	
        
        /* sync with a possible update in progress 
         * An update may "invalidate" a downloadable file, but this doesn't hurt - 
         * in that case we return a "file not found" :-)
         * The download itself does not need to be syncronized, because a DownloadItem 
         * contains only const data.
         */
        synchronized( sections )
	{
          di = findDownLoadItemByFilePart( path );
        }        
        
        // if we found a file... 
        if( di != null )
        {
          if( di.checkUser( requestUser ) )
          {
            // start a new download (if enougth connections are free)
            if( startNewDownload() )
            {
              try
              {  
                try
                {
                  putFile( di, res );
            
                  // log every successfull download !
                  log( "transfer to " + req.getRemoteUser() + "@" + req.getRemoteHost() + " (" + req.getRemoteAddr() + ") of " +
                     di.filename + " successfull." );
                }
                catch( IOException exc )
                {
                  d( "putFile IO err: " + exc );
            
                  throw exc;
                }  
              }
              finally
              {
                finishDownload();
              }
            }
            else
            {
              res.sendError( res.SC_SERVICE_UNAVAILABLE, "too many users - try again later" );
              d( "download rejected - too many users" );
            }
          }
          else
          {
            res.sendError( res.SC_FORBIDDEN, "access restricted" );
            d( "download rejected - user " + requestUser + " not authorized." );
          }
        }
        else
        {
          res.sendError( res.SC_NOT_FOUND, "file not found" );
        }
    }  
    
    
    private void putFile( DownloadItem di, HttpServletResponse res ) throws IOException
    {
        int sentsize = 0;
        
        d( "sending '" + di.filename + "' (" + di.contenttype + ")" );  
        	        
        // get source
        // InputStream         in  = new FileInputStream( di.filename );
        InputStream  in  = new BufferedInputStream( new FileInputStream( di.filename ) );
                
        // prepare and get destination
        res.setContentType(   di.contenttype   );
        res.setContentLength( (int)di.filesize ); // seems that here sits a possible problem for files >= 2 GB... 	  
        OutputStream out = res.getOutputStream();

/*               
        int i;
        while( (i = in.read()) != -1 )
        {
          out.write( i );
          sentsize++;
        }  

*/
        int  readlen;
        byte buffer[] = new byte[ 256 ];
                      
        // loop until EOF         
        while( (readlen = in.read( buffer )) != -1 )
        {
          // throws IOException: broken pipe when download is canceled.
          out.write( buffer, 0, readlen );
          sentsize += readlen;
        }  
        
        // Success ! Close streams. 
        out.flush();
        out.close();  
       
        in.close();      
        
        // log what happened...
        d( "transfer of " + di.filename + " (" + di.filesize + "/" + sentsize +  ") done." );  
    }
}  

  

Received on Monday, 22 May 2000 12:19:11 UTC