2009-09-23 23:26:14 +02:00
// HTTPDFileHandler.java
2009-07-19 22:37:44 +02:00
// -----------------------
// (C) by Michael Peter Christen; mc@yacy.net
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004, 2005
//
2009-09-23 23:26:14 +02:00
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
2009-07-19 22:37:44 +02:00
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/ *
Class documentation :
this class provides a file servlet and CGI interface
for the httpd server .
Whenever this server is addressed to load a local file ,
this class searches for the file in the local path as
configured in the setting property ' rootPath '
The servlet loads the file and returns it to the client .
Every file can also act as an template for the built - in
CGI interface . There is no specific path for CGI functions .
CGI functionality is triggered , if for the file to - be - served
' template . html ' also a file ' template . class ' exists . Then ,
the class file is called with the GET / POST properties that
are attached to the http call .
Possible variable hand - over are :
- form method GET
- form method POST , enctype text / plain
- form method POST , enctype multipart / form - data
The class that creates the CGI respond must have at least one
static method of the form
public static java . util . Hashtable respond ( java . util . HashMap , serverSwitch )
In the HashMap , the GET / POST variables are handed over .
The return value is a Property object that contains replacement
key / value pairs for the patterns in the template file .
The templates must have the form
either ' # [ ' < name > ' ] # ' for single attributes , or
' # { ' < enumname > ' } # ' and ' # { / ' < enumname > ' } # ' for enumerations of
values ' # [ ' < value > ' ] # ' .
A single value in repetitions / enumerations in the template has
the property key '_' < enumname > < count > '_' < value >
Please see also the example files ' test . html ' and ' test . java '
* /
package de.anomic.http.server ;
import java.awt.Image ;
import java.awt.image.BufferedImage ;
import java.io.BufferedInputStream ;
import java.io.BufferedOutputStream ;
2011-12-04 14:43:35 +01:00
import java.io.BufferedReader ;
2009-07-19 22:37:44 +02:00
import java.io.ByteArrayInputStream ;
2011-02-27 22:39:38 +01:00
import java.io.ByteArrayOutputStream ;
2009-07-19 22:37:44 +02:00
import java.io.File ;
import java.io.FileInputStream ;
2011-12-04 14:43:35 +01:00
import java.io.FileReader ;
2009-07-19 22:37:44 +02:00
import java.io.IOException ;
import java.io.InputStream ;
import java.io.OutputStream ;
2011-02-27 22:39:38 +01:00
import java.io.StringWriter ;
2009-07-19 22:37:44 +02:00
import java.lang.ref.SoftReference ;
import java.lang.reflect.InvocationTargetException ;
import java.lang.reflect.Method ;
2011-07-12 09:44:23 +02:00
import java.net.MalformedURLException ;
2011-02-27 22:39:38 +01:00
import java.net.URL ;
2012-02-24 17:02:57 +01:00
import java.net.URLDecoder ;
2011-12-04 14:43:35 +01:00
import java.util.ArrayList ;
import java.util.Arrays ;
2009-07-19 22:37:44 +02:00
import java.util.Date ;
2011-05-26 18:34:35 +02:00
import java.util.HashMap ;
2009-07-19 22:37:44 +02:00
import java.util.Iterator ;
2011-02-27 22:39:38 +01:00
import java.util.List ;
2009-07-19 22:37:44 +02:00
import java.util.Map ;
import java.util.concurrent.ConcurrentHashMap ;
2011-06-01 15:27:04 +02:00
import java.util.regex.Matcher ;
2011-02-27 22:39:38 +01:00
import java.util.regex.Pattern ;
2009-07-19 22:37:44 +02:00
import java.util.zip.GZIPOutputStream ;
2011-01-03 21:52:54 +01:00
import net.yacy.cora.date.GenericFormatter ;
2011-05-27 10:24:54 +02:00
import net.yacy.cora.document.ASCII ;
2012-04-21 21:31:13 +02:00
import net.yacy.cora.document.Classification ;
2011-06-29 11:32:02 +02:00
import net.yacy.cora.document.MultiProtocolURI ;
2011-03-07 21:36:40 +01:00
import net.yacy.cora.document.UTF8 ;
2010-08-23 14:32:02 +02:00
import net.yacy.cora.protocol.Domains ;
import net.yacy.cora.protocol.HeaderFramework ;
import net.yacy.cora.protocol.RequestHeader ;
import net.yacy.cora.protocol.ResponseHeader ;
2012-05-21 17:52:30 +02:00
import net.yacy.cora.util.NumberTools ;
2009-10-18 02:53:43 +02:00
import net.yacy.document.parser.htmlParser ;
import net.yacy.document.parser.html.ContentScraper ;
import net.yacy.document.parser.html.ScraperInputStream ;
2009-10-11 02:12:19 +02:00
import net.yacy.kelondro.data.meta.DigestURI ;
2009-10-10 01:13:30 +02:00
import net.yacy.kelondro.logging.Log ;
2011-05-05 02:25:14 +02:00
import net.yacy.kelondro.order.Digest ;
2009-10-10 03:14:19 +02:00
import net.yacy.kelondro.util.ByteBuffer ;
import net.yacy.kelondro.util.FileUtils ;
import net.yacy.kelondro.util.MemoryControl ;
2011-10-04 11:06:24 +02:00
import net.yacy.peers.Seed ;
2011-09-25 18:59:06 +02:00
import net.yacy.peers.graphics.EncodedImage ;
import net.yacy.peers.operation.yacyBuildProperties ;
import net.yacy.search.Switchboard ;
import net.yacy.search.SwitchboardConstants ;
2009-10-20 00:34:44 +02:00
import net.yacy.visualization.RasterPlotter ;
2011-09-20 16:54:14 +02:00
import de.anomic.data.UserDB ;
2009-07-19 22:37:44 +02:00
import de.anomic.server.serverClassLoader ;
import de.anomic.server.serverCore ;
import de.anomic.server.serverObjects ;
import de.anomic.server.serverSwitch ;
import de.anomic.server.servletProperties ;
public final class HTTPDFileHandler {
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// create a class loader
private static final serverClassLoader provider = new serverClassLoader ( /*this.getClass().getClassLoader()*/ ) ;
private static serverSwitch switchboard = null ;
private static Switchboard sb = Switchboard . getSwitchboard ( ) ;
2011-11-30 21:50:41 +01:00
private final static byte [ ] UNRESOLVED_PATTERN = ASCII . getBytes ( " -UNRESOLVED_PATTERN- " ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
private static File htRootPath = null ;
private static File htDocsPath = null ;
private static String [ ] defaultFiles = null ;
private static File htDefaultPath = null ;
private static File htLocalePath = null ;
2010-12-08 11:50:23 +01:00
public static String indexForward = " " ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
protected static final class TemplateCacheEntry {
Date lastModified ;
byte [ ] content ;
}
2011-06-21 00:53:43 +02:00
private static final ConcurrentHashMap < File , SoftReference < TemplateCacheEntry > > templateCache ;
2009-07-19 22:37:44 +02:00
private static final ConcurrentHashMap < File , SoftReference < Method > > templateMethodCache ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
public static final boolean useTemplateCache ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
//private Properties connectionProperties = null;
// creating a logger
private static final Log theLogger = new Log ( " FILEHANDLER " ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
static {
final serverSwitch theSwitchboard = Switchboard . getSwitchboard ( ) ;
useTemplateCache = theSwitchboard . getConfig ( " enableTemplateCache " , " true " ) . equalsIgnoreCase ( " true " ) ;
templateCache = ( useTemplateCache ) ? new ConcurrentHashMap < File , SoftReference < TemplateCacheEntry > > ( ) : new ConcurrentHashMap < File , SoftReference < TemplateCacheEntry > > ( 0 ) ;
2011-05-24 11:31:07 +02:00
templateMethodCache = new ConcurrentHashMap < File , SoftReference < Method > > ( ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( switchboard = = null ) {
switchboard = theSwitchboard ;
2009-09-23 23:26:14 +02:00
2011-09-01 18:05:00 +02:00
if ( Classification . countMimes ( ) = = 0 ) {
2009-07-19 22:37:44 +02:00
// load the mime table
2009-10-14 15:29:09 +02:00
final String mimeTablePath = theSwitchboard . getConfig ( " mimeTable " , " " ) ;
2009-11-23 17:10:50 +01:00
Log . logConfig ( " HTTPDFiles " , " Loading mime mapping file " + mimeTablePath ) ;
2011-09-01 18:05:00 +02:00
Classification . init ( new File ( theSwitchboard . getAppPath ( ) , mimeTablePath ) ) ;
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// create default files array
initDefaultPath ( ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// create a htRootPath: system pages
if ( htRootPath = = null ) {
2010-09-02 21:24:22 +02:00
htRootPath = new File ( theSwitchboard . getAppPath ( ) , theSwitchboard . getConfig ( SwitchboardConstants . HTROOT_PATH , SwitchboardConstants . HTROOT_PATH_DEFAULT ) ) ;
2009-07-19 22:37:44 +02:00
if ( ! ( htRootPath . exists ( ) ) ) htRootPath . mkdir ( ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// create a htDocsPath: user defined pages
if ( htDocsPath = = null ) {
2010-09-02 21:24:22 +02:00
htDocsPath = theSwitchboard . getDataPath ( SwitchboardConstants . HTDOCS_PATH , SwitchboardConstants . HTDOCS_PATH_DEFAULT ) ;
2009-07-19 22:37:44 +02:00
if ( ! ( htDocsPath . exists ( ) ) ) htDocsPath . mkdirs ( ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// create a repository path
final File repository = new File ( htDocsPath , " repository " ) ;
if ( ! repository . exists ( ) ) repository . mkdirs ( ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// create htLocaleDefault, htLocalePath
2010-09-02 21:24:22 +02:00
if ( htDefaultPath = = null ) htDefaultPath = theSwitchboard . getAppPath ( " htDefaultPath " , " htroot " ) ;
if ( htLocalePath = = null ) htLocalePath = theSwitchboard . getDataPath ( " locale.translated_html " , " DATA/LOCALE/htroot " ) ;
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
public static final void initDefaultPath ( ) {
// create default files array
2012-02-26 20:53:32 +01:00
defaultFiles = switchboard . getConfig ( SwitchboardConstants . BROWSER_DEFAULT , " index.html " ) . split ( " , " ) ;
2009-07-19 22:37:44 +02:00
if ( defaultFiles . length = = 0 ) defaultFiles = new String [ ] { " index.html " } ;
2011-02-03 11:43:09 +01:00
indexForward = switchboard . getConfig ( SwitchboardConstants . INDEX_FORWARD , " " ) ;
2010-12-08 11:50:23 +01:00
if ( indexForward . startsWith ( " / " ) ) indexForward = indexForward . substring ( 1 ) ;
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
/ * * Returns a path to the localized or default file according to the locale . language ( from he switchboard )
* @param path relative from htroot * /
public static File getLocalizedFile ( final String path ) {
return getLocalizedFile ( path , switchboard . getConfig ( " locale.language " , " default " ) ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
/ * * Returns a path to the localized or default file according to the parameter localeSelection
* @param path relative from htroot
* @param localeSelection language of localized file ; locale . language from switchboard is used if localeSelection . equals ( " " ) * /
public static File getLocalizedFile ( final String path , final String localeSelection ) {
//if (htDefaultPath == null) htDefaultPath = switchboard.getConfigPath("htDefaultPath", "htroot");
//if (htLocalePath == null) htLocalePath = switchboard.getConfigPath("locale.translated_html", "DATA/LOCALE/htroot");
//if (htDocsPath == null) htDocsPath = switchboard.getConfigPath(plasmaSwitchboardConstants.HTDOCS_PATH, plasmaSwitchboardConstants.HTDOCS_PATH_DEFAULT);
if ( path . startsWith ( " /repository/ " ) )
return new File ( switchboard . getConfig ( " repositoryPath " , " DATA/HTDOCS/repository " ) , path . substring ( 11 ) ) ;
if ( ! ( localeSelection . equals ( " default " ) ) ) {
final File localePath = new File ( htLocalePath , localeSelection + '/' + path ) ;
if ( localePath . exists ( ) ) return localePath ; // avoid "NoSuchFile" troubles if the "localeSelection" is misspelled
}
2011-06-21 00:53:43 +02:00
final File docsPath = new File ( htDocsPath , path ) ;
2009-07-19 22:37:44 +02:00
if ( docsPath . exists ( ) ) return docsPath ;
return new File ( htDefaultPath , path ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
private static final ResponseHeader getDefaultHeaders ( final String path ) {
final ResponseHeader headers = new ResponseHeader ( ) ;
String ext ;
int pos ;
if ( ( pos = path . lastIndexOf ( '.' ) ) < 0 ) {
ext = " " ;
} else {
ext = path . substring ( pos + 1 ) . toLowerCase ( ) ;
}
headers . put ( HeaderFramework . SERVER , " AnomicHTTPD (www.anomic.de) " ) ;
2010-08-23 14:32:02 +02:00
headers . put ( HeaderFramework . DATE , HeaderFramework . formatRFC1123 ( new Date ( ) ) ) ;
2009-07-19 22:37:44 +02:00
if ( ! ( Classification . isMediaExtension ( ext ) ) ) {
2011-06-21 00:53:43 +02:00
headers . put ( HeaderFramework . PRAGMA , " no-cache " ) ;
2009-07-19 22:37:44 +02:00
}
return headers ;
}
2011-06-21 00:53:43 +02:00
2011-05-26 18:34:35 +02:00
public static void doGet ( final HashMap < String , Object > conProp , final RequestHeader requestHeader , final OutputStream response ) {
2009-07-19 22:37:44 +02:00
doResponse ( conProp , requestHeader , response , null ) ;
}
2011-06-21 00:53:43 +02:00
2011-05-26 18:34:35 +02:00
public static void doHead ( final HashMap < String , Object > conProp , final RequestHeader requestHeader , final OutputStream response ) {
2009-07-19 22:37:44 +02:00
doResponse ( conProp , requestHeader , response , null ) ;
}
2011-06-21 00:53:43 +02:00
2011-05-26 18:34:35 +02:00
public static void doPost ( final HashMap < String , Object > conProp , final RequestHeader requestHeader , final OutputStream response , final InputStream body ) {
2009-07-19 22:37:44 +02:00
doResponse ( conProp , requestHeader , response , body ) ;
}
2011-06-21 00:53:43 +02:00
2011-05-26 18:34:35 +02:00
public static void doResponse ( final HashMap < String , Object > conProp , final RequestHeader requestHeader , final OutputStream out , final InputStream body ) {
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
String path = null ;
try {
2011-06-21 00:53:43 +02:00
// getting some connection properties
2011-05-26 18:34:35 +02:00
final String method = ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_METHOD ) ;
path = ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_PATH ) ;
String argsString = ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_ARGS ) ; // is null if no args were given
final String httpVersion = ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_HTTP_VER ) ;
String clientIP = ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_CLIENTIP ) ; if ( clientIP = = null ) clientIP = " unknown-host " ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// check hack attacks in path
2011-11-25 12:23:52 +01:00
if ( path . indexOf ( " .. " , 0 ) > = 0 ) {
2009-07-19 22:37:44 +02:00
HTTPDemon . sendRespondError ( conProp , out , 4 , 403 , null , " Access not allowed " , null ) ;
return ;
}
2011-06-21 00:53:43 +02:00
2011-11-08 13:02:22 +01:00
path = UTF8 . decodeURL ( path ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// check against hack attacks in path
2011-11-25 12:23:52 +01:00
if ( path . indexOf ( " .. " , 0 ) > = 0 ) {
2009-07-19 22:37:44 +02:00
HTTPDemon . sendRespondError ( conProp , out , 4 , 403 , null , " Access not allowed " , null ) ;
return ;
}
2011-06-21 00:53:43 +02:00
2012-06-10 12:31:49 +02:00
// allow proper access to current peer via virtual directory
if ( path . startsWith ( " /currentyacypeer/ " ) ) {
path = path . substring ( 16 ) ;
}
2011-06-21 00:53:43 +02:00
2010-09-29 21:56:42 +02:00
// cache settings
boolean nocache = path . contains ( " ? " ) | | body ! = null ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// a bad patch to map the /xml/ path to /api/
if ( path . startsWith ( " /xml/ " ) ) {
path = " /api/ " + path . substring ( 5 ) ;
}
2010-01-22 15:20:24 +01:00
// another bad patch to map the /util/ path to /api/util/ to support old yacybars
if ( path . startsWith ( " /util/ " ) ) {
path = " /api/util/ " + path . substring ( 6 ) ;
}
// one more for bookmarks
if ( path . startsWith ( " /bookmarks/ " ) ) {
path = " /api/bookmarks/ " + path . substring ( 11 ) ;
}
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// these are the 5 cases where an access granted:
// (the alternative is that we deliver a 401 to request authorization)
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// -1- the page is not protected; or
2011-11-25 12:23:52 +01:00
final boolean protectedPage = path . indexOf ( " _p. " , 0 ) > 0 ;
2011-05-05 02:25:14 +02:00
boolean accessGranted = ! protectedPage ;
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// -2- a password is not configured; or
final String adminAccountBase64MD5 = switchboard . getConfig ( SwitchboardConstants . ADMIN_ACCOUNT_B64MD5 , " " ) ;
if ( ! accessGranted ) {
accessGranted = adminAccountBase64MD5 . length ( ) = = 0 ;
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// -3- access from localhost is granted and access comes from localhost; or
final String refererHost = requestHeader . refererHost ( ) ;
if ( ! accessGranted ) {
final boolean adminAccountForLocalhost = sb . getConfigBool ( " adminAccountForLocalhost " , false ) ;
2011-06-21 00:53:43 +02:00
final boolean accessFromLocalhost = Domains . isLocalhost ( clientIP ) & & ( refererHost = = null | | refererHost . length ( ) = = 0 | | Domains . isLocalhost ( refererHost ) ) ;
2011-05-05 02:25:14 +02:00
accessGranted = adminAccountForLocalhost & & accessFromLocalhost ;
}
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// -4- a password is configured and access comes from localhost
// and the realm-value of a http-authentify String is equal to the stored base64MD5; or
String realmProp = requestHeader . get ( RequestHeader . AUTHORIZATION ) ;
if ( realmProp ! = null & & realmProp . length ( ) = = 0 ) realmProp = null ;
2011-06-21 00:53:43 +02:00
final String realmValue = realmProp = = null ? null : realmProp . substring ( 6 ) ;
2011-05-05 02:25:14 +02:00
if ( ! accessGranted ) {
2011-06-21 00:53:43 +02:00
final boolean accessFromLocalhost = Domains . isLocalhost ( clientIP ) & & ( refererHost = = null | | refererHost . length ( ) = = 0 | | Domains . isLocalhost ( refererHost ) ) ;
2011-05-05 02:25:14 +02:00
accessGranted = accessFromLocalhost & & realmValue ! = null & & realmProp . length ( ) > 6 & & ( adminAccountBase64MD5 . equals ( realmValue ) ) ;
}
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// -5- a password is configured and access comes with matching http-authentify
if ( ! accessGranted ) {
accessGranted = realmProp ! = null & & realmValue ! = null & & ( sb . userDB . hasAdminRight ( realmProp , requestHeader . getHeaderCookies ( ) ) | | adminAccountBase64MD5 . equals ( Digest . encodeMD5Hex ( realmValue ) ) ) ;
}
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
// in case that we are still not granted we ask for a password
if ( ! accessGranted ) {
Log . logInfo ( " HTTPD " , " Wrong log-in for path ' " + path + " ' from host ' " + clientIP + " ' " ) ;
final Integer attempts = serverCore . bfHost . get ( clientIP ) ;
if ( attempts = = null )
serverCore . bfHost . put ( clientIP , Integer . valueOf ( 1 ) ) ;
else
serverCore . bfHost . put ( clientIP , Integer . valueOf ( attempts . intValue ( ) + 1 ) ) ;
2011-06-21 00:53:43 +02:00
2011-05-05 02:25:14 +02:00
final ResponseHeader responseHeader = getDefaultHeaders ( path ) ;
2012-05-24 13:24:31 +02:00
responseHeader . put ( RequestHeader . WWW_AUTHENTICATE , " Basic realm= \" " + serverObjects . ADMIN_AUTHENTICATE_MSG + " \" " ) ;
2011-05-05 02:25:14 +02:00
final servletProperties tp = new servletProperties ( ) ;
tp . put ( " returnto " , path ) ;
HTTPDemon . sendRespondError ( conProp , out , 5 , 401 , " Wrong Authentication " , " " , new File ( " proxymsg/authfail.inc " ) , tp , null , responseHeader ) ;
return ;
}
// Authentication successful. remove brute-force flag
2011-05-26 18:34:35 +02:00
serverCore . bfHost . remove ( conProp . get ( HeaderFramework . CONNECTION_PROP_CLIENTIP ) ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// parse arguments
serverObjects args = new serverObjects ( ) ;
int argc = 0 ;
if ( argsString = = null ) {
// no args here, maybe a POST with multipart extension
2011-06-21 00:53:43 +02:00
final int length = requestHeader . getContentLength ( ) ;
2009-07-19 22:37:44 +02:00
//System.out.println("HEADER: " + requestHeader.toString()); // DEBUG
/ * don ' t parse body in case of a POST CGI call since it has to be
* handed over to the CGI script unaltered and parsed by the script
* /
if ( method . equals ( HeaderFramework . METHOD_POST ) & &
! ( switchboard . getConfigBool ( " cgi.allow " , false ) & &
matchesSuffix ( path , switchboard . getConfig ( " cgi.suffixes " , null ) ) )
) {
// if its a POST, it can be either multipart or as args in the body
if ( ( requestHeader . containsKey ( HeaderFramework . CONTENT_TYPE ) ) & &
( requestHeader . get ( HeaderFramework . CONTENT_TYPE ) . toLowerCase ( ) . startsWith ( " multipart " ) ) ) {
// parse multipart
2010-06-01 15:02:11 +02:00
final Map < String , byte [ ] > files = HTTPDemon . parseMultipart ( requestHeader , args , body ) ;
2009-07-19 22:37:44 +02:00
// integrate these files into the args
if ( files ! = null ) {
final Iterator < Map . Entry < String , byte [ ] > > fit = files . entrySet ( ) . iterator ( ) ;
Map . Entry < String , byte [ ] > entry ;
while ( fit . hasNext ( ) ) {
entry = fit . next ( ) ;
args . put ( entry . getKey ( ) + " $file " , entry . getValue ( ) ) ;
}
}
argc = Integer . parseInt ( requestHeader . get ( " ARGC " ) ) ;
} else {
// parse args in body
argc = HTTPDemon . parseArgs ( args , body , length ) ;
}
} else {
// no args
argsString = null ;
args = null ;
argc = 0 ;
}
} else {
// simple args in URL (stuff after the "?")
argc = HTTPDemon . parseArgs ( args , argsString ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// check for cross site scripting - attacks in request arguments
if ( args ! = null & & argc > 0 ) {
// check all values for occurrences of script values
final Iterator < String > e = args . values ( ) . iterator ( ) ; // enumeration of values
String val ;
while ( e . hasNext ( ) ) {
val = e . next ( ) ;
2011-11-25 12:23:52 +01:00
if ( ( val ! = null ) & & ( val . indexOf ( " <script " , 0 ) > = 0 ) & & ! path . equals ( " /Crawler_p.html " ) ) {
2009-07-19 22:37:44 +02:00
// deny request
HTTPDemon . sendRespondError ( conProp , out , 4 , 403 , null , " bad post values " , null ) ;
return ;
}
}
}
2011-06-21 00:53:43 +02:00
2010-09-29 21:56:42 +02:00
if ( args ! = null ) nocache = true ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// we are finished with parsing
// the result of value hand-over is in args and argc
2012-02-14 20:54:19 +01:00
if ( path . isEmpty ( ) ) {
2009-07-19 22:37:44 +02:00
HTTPDemon . sendRespondError ( conProp , out , 4 , 400 , null , " Bad Request " , null ) ;
out . flush ( ) ;
return ;
}
2012-02-14 20:54:19 +01:00
File targetClass = null ;
2009-07-19 22:37:44 +02:00
// locate the file
2012-02-14 20:54:19 +01:00
if ( ! path . isEmpty ( ) & & path . charAt ( 0 ) ! = '/' & & path . charAt ( 0 ) ! = '\\' ) {
path = " / " + path ; // attach leading slash
}
if ( path . endsWith ( " index.html " ) ) {
path = path . substring ( 0 , path . length ( ) - 10 ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// a different language can be desired (by i.e. ConfigBasic.html) than the one stored in the locale.language
String localeSelection = switchboard . getConfig ( " locale.language " , " default " ) ;
if ( args ! = null & & ( args . containsKey ( " language " ) ) ) {
2011-06-21 00:53:43 +02:00
// TODO 9.11.06 Bost: a class with information about available languages is needed.
2009-07-19 22:37:44 +02:00
// the indexOf(".") is just a workaround because there from ConfigLanguage.html commes "de.lng" and
// from ConfigBasic.html comes just "de" in the "language" parameter
localeSelection = args . get ( " language " , localeSelection ) ;
2010-01-13 01:23:07 +01:00
if ( localeSelection . indexOf ( '.' ) ! = - 1 )
localeSelection = localeSelection . substring ( 0 , localeSelection . indexOf ( '.' ) ) ;
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
File targetFile = getLocalizedFile ( path , localeSelection ) ;
2011-05-26 18:34:35 +02:00
String targetExt = ( String ) conProp . get ( " EXT " ) ; if ( targetExt = = null ) targetExt = " " ;
2009-07-19 22:37:44 +02:00
targetClass = rewriteClassFile ( new File ( htDefaultPath , path ) ) ;
if ( path . endsWith ( " / " ) | | path . endsWith ( " \\ " ) ) {
String testpath ;
2010-12-08 11:50:23 +01:00
// look for indexForward setting
if ( indexForward . length ( ) > 0 & & ( targetFile = getOverlayedFile ( path + indexForward ) ) . exists ( ) ) {
testpath = path + indexForward ;
2009-07-19 22:37:44 +02:00
targetClass = getOverlayedClass ( testpath ) ;
2010-12-08 11:50:23 +01:00
path = testpath ;
} else {
// attach default file name(s)
2011-06-21 00:53:43 +02:00
for ( final String defaultFile : defaultFiles ) {
testpath = path + defaultFile ;
2010-12-08 11:50:23 +01:00
targetFile = getOverlayedFile ( testpath ) ;
targetClass = getOverlayedClass ( testpath ) ;
if ( targetFile . exists ( ) ) {
path = testpath ;
break ;
}
2009-07-19 22:37:44 +02:00
}
}
targetFile = getLocalizedFile ( path , localeSelection ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
//no defaultfile, send a dirlisting
if ( targetFile = = null | | ! targetFile . exists ( ) | | ( targetFile . exists ( ) & & targetFile . isDirectory ( ) ) ) {
final StringBuilder aBuffer = new StringBuilder ( ) ;
aBuffer . append ( " <html> \ n<head> \ n</head> \ n<body> \ n<h1>Index of " + path + " </h1> \ n <ul> \ n " ) ;
String [ ] list = targetFile . list ( ) ;
if ( list = = null ) list = new String [ 0 ] ; // should not occur!
File f ;
String size ;
long sz ;
2010-05-11 13:14:05 +02:00
String headline , author , description , publisher ;
2009-07-19 22:37:44 +02:00
int images , links ;
ContentScraper scraper ;
2011-06-21 00:53:43 +02:00
for ( final String element : list ) {
f = new File ( targetFile , element ) ;
2009-07-19 22:37:44 +02:00
if ( f . isDirectory ( ) ) {
2011-06-21 00:53:43 +02:00
aBuffer . append ( " <li><a href= \" " + path + element + " / \" > " + element + " /</a><br/></li> \ n " ) ;
2009-07-19 22:37:44 +02:00
} else {
2011-06-21 00:53:43 +02:00
if ( element . endsWith ( " html " ) | | ( element . endsWith ( " htm " ) ) ) {
2009-07-19 22:37:44 +02:00
scraper = ContentScraper . parseResource ( f ) ;
headline = scraper . getTitle ( ) ;
author = scraper . getAuthor ( ) ;
2010-05-11 13:14:05 +02:00
publisher = scraper . getPublisher ( ) ;
2009-07-19 22:37:44 +02:00
description = scraper . getDescription ( ) ;
images = scraper . getImages ( ) . size ( ) ;
links = scraper . getAnchors ( ) . size ( ) ;
} else {
headline = null ;
author = null ;
2010-05-11 13:14:05 +02:00
publisher = null ;
2009-07-19 22:37:44 +02:00
description = null ;
images = 0 ;
links = 0 ;
}
sz = f . length ( ) ;
if ( sz < 1024 ) {
size = sz + " bytes " ;
} else if ( sz < 1024 * 1024 ) {
size = ( sz / 1024 ) + " KB " ;
} else {
size = ( sz / 1024 / 1024 ) + " MB " ;
}
aBuffer . append ( " <li> " ) ;
2011-06-21 00:53:43 +02:00
if ( headline ! = null & & headline . length ( ) > 0 ) aBuffer . append ( " <a href= \" " + element + " \" ><b> " + headline + " </b></a><br/> " ) ;
aBuffer . append ( " <a href= \" " + path + element + " \" > " + element + " </a><br/> " ) ;
2010-12-15 20:20:00 +01:00
if ( author ! = null & & author . length ( ) > 0 ) aBuffer . append ( " Author: " + author + " <br/> " ) ;
if ( publisher ! = null & & publisher . length ( ) > 0 ) aBuffer . append ( " Publisher: " + publisher + " <br/> " ) ;
if ( description ! = null & & description . length ( ) > 0 ) aBuffer . append ( " Description: " + description + " <br/> " ) ;
2011-01-03 21:52:54 +01:00
aBuffer . append ( GenericFormatter . SHORT_DAY_FORMATTER . format ( new Date ( f . lastModified ( ) ) ) + " , " + size + ( ( images > 0 ) ? " , " + images + " images " : " " ) + ( ( links > 0 ) ? " , " + links + " links " : " " ) + " <br/></li> \ n " ) ;
2009-07-19 22:37:44 +02:00
}
}
aBuffer . append ( " </ul> \ n</body> \ n</html> \ n " ) ;
// write the list to the client
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 200 , null , " text/html; charset=UTF-8 " , aBuffer . length ( ) , new Date ( targetFile . lastModified ( ) ) , null , new ResponseHeader ( ) , null , null , true ) ;
if ( ! method . equals ( HeaderFramework . METHOD_HEAD ) ) {
2011-03-11 00:25:07 +01:00
out . write ( UTF8 . getBytes ( aBuffer . toString ( ) ) ) ;
2009-07-19 22:37:44 +02:00
}
return ;
}
} else {
//XXX: you cannot share a .png/.gif file with a name like a class in htroot.
if ( ! ( targetFile . exists ( ) ) & &
! ( ( path . endsWith ( " png " ) | | path . endsWith ( " gif " ) | |
matchesSuffix ( path , switchboard . getConfig ( " cgi.suffixes " , null ) ) | |
path . endsWith ( " .stream " ) ) & &
targetClass ! = null ) ) {
targetFile = new File ( htDocsPath , path ) ;
targetClass = rewriteClassFile ( new File ( htDocsPath , path ) ) ;
}
}
2011-06-21 00:53:43 +02:00
2011-02-27 22:39:38 +01:00
// implement proxy via url (not in servlet, because we need binary access on ouputStream)
if ( path . equals ( " /proxy.html " ) ) {
2011-06-21 00:53:43 +02:00
final List < Pattern > urlProxyAccess = Domains . makePatterns ( sb . getConfig ( " proxyURL.access " , " 127.0.0.1 " ) ) ;
2011-11-08 13:02:22 +01:00
final UserDB . Entry user = sb . userDB . getUser ( requestHeader ) ;
final boolean user_may_see_proxyurl = Domains . matchesList ( clientIP , urlProxyAccess ) | | ( user ! = null & & user . hasRight ( UserDB . AccessRight . PROXY_RIGHT ) ) ;
2011-09-20 16:54:14 +02:00
if ( sb . getConfigBool ( " proxyURL " , false ) & & user_may_see_proxyurl ) {
2011-02-27 22:39:38 +01:00
doURLProxy ( args , conProp , requestHeader , out ) ;
return ;
}
else {
HTTPDemon . sendRespondError ( conProp , out , 3 , 403 , " Access denied " , null , null ) ;
}
}
2011-06-21 00:53:43 +02:00
2011-01-27 12:17:11 +01:00
// track all files that had been accessed so far
if ( targetFile ! = null & & targetFile . exists ( ) ) {
if ( args ! = null & & args . size ( ) > 0 ) sb . setConfig ( " server.servlets.submitted " , appendPath ( sb . getConfig ( " server.servlets.submitted " , " " ) , path ) ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
//File targetClass = rewriteClassFile(targetFile);
//We need tp here
servletProperties templatePatterns = null ;
Date targetDate ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( ( targetClass ! = null ) & & ( path . endsWith ( " png " ) ) ) {
// call an image-servlet to produce an on-the-fly - generated image
Object img = null ;
try {
2011-05-26 18:34:35 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_CLIENTIP , ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_CLIENTIP ) ) ;
2009-07-19 22:37:44 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_PATH , path ) ;
2010-05-11 13:14:05 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_EXT , " png " ) ;
2009-07-19 22:37:44 +02:00
// in case that there are no args given, args = null or empty hashmap
img = invokeServlet ( targetClass , requestHeader , args ) ;
} catch ( final InvocationTargetException e ) {
theLogger . logSevere ( " INTERNAL ERROR: " + e . toString ( ) + " : " +
e . getMessage ( ) +
" target exception at " + targetClass + " : " +
e . getTargetException ( ) . toString ( ) + " : " +
e . getTargetException ( ) . getMessage ( ) +
" ; java.awt.graphicsenv=' " + System . getProperty ( " java.awt.graphicsenv " , " " ) + " ' " ) ;
2009-11-05 21:28:37 +01:00
Log . logException ( e ) ;
2012-05-30 15:27:45 +02:00
Log . logException ( e . getCause ( ) ) ;
2009-11-05 21:28:37 +01:00
Log . logException ( e . getTargetException ( ) ) ;
2009-07-19 22:37:44 +02:00
targetClass = null ;
}
if ( img = = null ) {
// error with image generation; send file-not-found
HTTPDemon . sendRespondError ( conProp , out , 3 , 404 , " File not Found " , null , null ) ;
} else {
2009-10-20 00:34:44 +02:00
if ( img instanceof RasterPlotter ) {
final RasterPlotter yp = ( RasterPlotter ) img ;
2009-07-19 22:37:44 +02:00
// send an image to client
targetDate = new Date ( System . currentTimeMillis ( ) ) ;
nocache = true ;
2011-09-01 18:05:00 +02:00
final String mimeType = Classification . ext2mime ( targetExt , " text/html " ) ;
2009-10-20 00:34:44 +02:00
final ByteBuffer result = RasterPlotter . exportImage ( yp . getImage ( ) , targetExt ) ;
2009-07-19 22:37:44 +02:00
// write the array to the client
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 200 , null , mimeType , result . length ( ) , targetDate , null , null , null , null , nocache ) ;
2010-07-28 14:45:53 +02:00
if ( ! method . equals ( HeaderFramework . METHOD_HEAD ) ) {
result . writeTo ( out ) ;
}
}
if ( img instanceof EncodedImage ) {
final EncodedImage yp = ( EncodedImage ) img ;
// send an image to client
targetDate = new Date ( System . currentTimeMillis ( ) ) ;
nocache = true ;
2011-09-01 18:05:00 +02:00
final String mimeType = Classification . ext2mime ( targetExt , " text/html " ) ;
2010-07-28 14:45:53 +02:00
final ByteBuffer result = yp . getImage ( ) ;
// write the array to the client
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 200 , null , mimeType , result . length ( ) , targetDate , null , null , null , null , nocache ) ;
2009-07-19 22:37:44 +02:00
if ( ! method . equals ( HeaderFramework . METHOD_HEAD ) ) {
result . writeTo ( out ) ;
}
}
2011-05-07 09:37:46 +02:00
/ *
if ( img instanceof BufferedImage ) {
final BufferedImage i = ( BufferedImage ) img ;
// send an image to client
targetDate = new Date ( System . currentTimeMillis ( ) ) ;
nocache = true ;
final String mimeType = MimeTable . ext2mime ( targetExt , " text/html " ) ;
// generate an byte array from the generated image
int width = i . getWidth ( ) ; if ( width < 0 ) width = 96 ; // bad hack
int height = i . getHeight ( ) ; if ( height < 0 ) height = 96 ; // bad hack
final ByteBuffer result = RasterPlotter . exportImage ( i , targetExt ) ;
// write the array to the client
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 200 , null , mimeType , result . length ( ) , targetDate , null , null , null , null , nocache ) ;
if ( ! method . equals ( HeaderFramework . METHOD_HEAD ) ) {
result . writeTo ( out ) ;
}
}
* /
2009-07-19 22:37:44 +02:00
if ( img instanceof Image ) {
final Image i = ( Image ) img ;
// send an image to client
targetDate = new Date ( System . currentTimeMillis ( ) ) ;
nocache = true ;
2011-09-01 18:05:00 +02:00
final String mimeType = Classification . ext2mime ( targetExt , " text/html " ) ;
2009-07-19 22:37:44 +02:00
// generate an byte array from the generated image
int width = i . getWidth ( null ) ; if ( width < 0 ) width = 96 ; // bad hack
int height = i . getHeight ( null ) ; if ( height < 0 ) height = 96 ; // bad hack
final BufferedImage bi = new BufferedImage ( width , height , BufferedImage . TYPE_INT_RGB ) ;
2011-06-21 00:53:43 +02:00
bi . createGraphics ( ) . drawImage ( i , 0 , 0 , width , height , null ) ;
2009-10-20 00:34:44 +02:00
final ByteBuffer result = RasterPlotter . exportImage ( bi , targetExt ) ;
2009-07-19 22:37:44 +02:00
// write the array to the client
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 200 , null , mimeType , result . length ( ) , targetDate , null , null , null , null , nocache ) ;
if ( ! method . equals ( HeaderFramework . METHOD_HEAD ) ) {
result . writeTo ( out ) ;
}
}
}
2011-12-04 14:43:35 +01:00
// old-school CGI execution
} else if ( ( switchboard . getConfigBool ( " cgi.allow " , false ) // check if CGI execution is allowed in config
& & matchesSuffix ( path , switchboard . getConfig ( " cgi.suffixes " , null ) ) // "right" file extension?
& & path . substring ( 0 , path . indexOf ( targetFile . getName ( ) ) ) . toUpperCase ( ) . contains ( " /CGI-BIN/ " ) // file in right directory?
& & targetFile . exists ( ) )
2009-07-19 22:37:44 +02:00
) {
2011-12-04 14:43:35 +01:00
if ( ! targetFile . canExecute ( ) ) {
HTTPDemon . sendRespondError (
conProp ,
out ,
- 1 ,
403 ,
null ,
HeaderFramework . http1_1 . get (
Integer . toString ( 403 ) ) ,
null ) ;
Log . logWarning (
" HTTPD " ,
" CGI script " + targetFile . getPath ( )
+ " could not be executed due to "
+ " insufficient access rights. " ) ;
} else {
String mimeType = " text/html " ;
int statusCode = 200 ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
final ProcessBuilder pb =
new ProcessBuilder ( assembleCommandFromShebang ( targetFile ) ) ;
pb . directory ( targetFile . getParentFile ( ) ) ;
final String fileSeparator =
System . getProperty ( " file.separator " , " / " ) ;
// set environment variables
final Map < String , String > env = pb . environment ( ) ;
env . put (
" SERVER_SOFTWARE " ,
getDefaultHeaders ( path ) . get ( HeaderFramework . SERVER ) ) ;
env . put ( " SERVER_NAME " , sb . peers . mySeed ( ) . getName ( ) ) ;
env . put ( " GATEWAY_INTERFACE " , " CGI/1.1 " ) ;
if ( httpVersion ! = null ) {
env . put ( " SERVER_PROTOCOL " , httpVersion ) ;
}
env . put ( " SERVER_PORT " , switchboard . getConfig ( " port " , " 8090 " ) ) ;
env . put ( " REQUEST_METHOD " , method ) ;
// env.put("PATH_INFO", ""); // TODO: implement
// env.put("PATH_TRANSLATED", ""); // TODO: implement
env . put ( " SCRIPT_NAME " , path ) ;
if ( argsString ! = null ) {
env . put ( " QUERY_STRING " , argsString ) ;
}
env . put ( " REMOTE_ADDR " , clientIP ) ;
// env.put("AUTH_TYPE", ""); // TODO: implement
// env.put("REMOTE_USER", ""); // TODO: implement
// env.put("REMOTE_IDENT", ""); // I don't think we need this
env . put (
" DOCUMENT_ROOT " ,
switchboard . getAppPath ( ) . getAbsolutePath ( )
+ fileSeparator + switchboard . getConfig ( " htDocsPath " , " DATA/HTDOCS " ) ) ;
if ( requestHeader . getContentType ( ) ! = null ) {
env . put ( " CONTENT_TYPE " , requestHeader . getContentType ( ) ) ;
}
if ( method . equalsIgnoreCase ( HeaderFramework . METHOD_POST )
& & body ! = null ) {
env . put (
" CONTENT_LENGTH " ,
Integer . toString ( requestHeader . getContentLength ( ) ) ) ;
}
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
/ * add values from request header to environment
* ( see : http : //hoohoo.ncsa.uiuc.edu/cgi/env.html#headers) */
for ( final Map . Entry < String , String > requestHeaderEntry
: requestHeader . entrySet ( ) ) {
env . put ( " HTTP_ "
+ requestHeaderEntry . getKey ( ) . toUpperCase ( ) . replace ( " - " , " _ " ) ,
requestHeaderEntry . getValue ( ) ) ;
}
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
int exitValue = 0 ;
String cgiBody = null ;
final StringBuilder error = new StringBuilder ( 256 ) ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
try {
// start execution of script
final Process p = pb . start ( ) ;
final OutputStream os =
new BufferedOutputStream ( p . getOutputStream ( ) ) ;
if ( method . equalsIgnoreCase (
HeaderFramework . METHOD_POST ) & & body ! = null ) {
final byte [ ] buffer = new byte [ 1024 ] ;
int len = requestHeader . getContentLength ( ) ;
while ( len > 0 ) {
body . read ( buffer ) ;
len = len - buffer . length ;
os . write ( buffer ) ;
}
}
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
os . close ( ) ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
try {
p . waitFor ( ) ;
} catch ( final InterruptedException ex ) {
2009-07-19 22:37:44 +02:00
}
2011-12-04 14:43:35 +01:00
exitValue = p . exitValue ( ) ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
final InputStream is =
new BufferedInputStream ( p . getInputStream ( ) ) ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
final InputStream es =
new BufferedInputStream ( p . getErrorStream ( ) ) ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
final StringBuilder processOutput =
new StringBuilder ( 1024 ) ;
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
while ( is . available ( ) > 0 ) {
processOutput . append ( ( char ) is . read ( ) ) ;
}
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
while ( es . available ( ) > 0 ) {
error . append ( ( char ) es . read ( ) ) ;
}
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
int indexOfDelimiter = processOutput . indexOf ( " \ n \ n " , 0 ) ;
final String [ ] cgiHeader ;
if ( indexOfDelimiter > - 1 ) {
cgiHeader =
processOutput . substring (
0 , indexOfDelimiter ) . split ( " \ n " ) ;
} else {
cgiHeader = new String [ 0 ] ;
}
cgiBody = processOutput . substring ( indexOfDelimiter + 1 ) ;
String key ;
String value ;
for ( final String element : cgiHeader ) {
indexOfDelimiter = element . indexOf ( ':' ) ;
key = element . substring ( 0 , indexOfDelimiter ) . trim ( ) ;
value = element . substring ( indexOfDelimiter + 1 ) . trim ( ) ;
conProp . put ( key , value ) ;
if ( " Cache-Control " . equals ( key )
& & " no-cache " . equals ( value ) ) {
nocache = true ;
} else if ( " Content-type " . equals ( key ) ) {
mimeType = value ;
} else if ( " Status " . equals ( key ) ) {
if ( key . length ( ) > 2 ) {
try {
statusCode =
Integer . parseInt (
value . substring ( 0 , 3 ) ) ;
} catch ( final NumberFormatException ex ) {
Log . logWarning (
" HTTPD " ,
" CGI script " + targetFile . getPath ( )
+ " returned illegal status code \" "
+ value + " \" . " ) ;
}
2009-07-19 22:37:44 +02:00
}
}
}
2011-12-04 14:43:35 +01:00
} catch ( final IOException ex ) {
exitValue = - 1 ;
2009-07-19 22:37:44 +02:00
}
2011-12-04 14:43:35 +01:00
/ * did the script return an exit value ! = 0
* and still there is supposed to be
* everything right with the HTTP status ?
* - > change status to 500 since 200 would
* be a lie
* /
if ( exitValue ! = 0 & & statusCode = = 200 ) {
statusCode = 500 ;
}
2009-07-19 22:37:44 +02:00
2011-12-04 14:43:35 +01:00
targetDate = new Date ( System . currentTimeMillis ( ) ) ;
2012-02-14 20:54:19 +01:00
if ( cgiBody ! = null & & ! cgiBody . isEmpty ( ) ) {
2011-12-04 14:43:35 +01:00
HTTPDemon . sendRespondHeader (
conProp ,
out ,
httpVersion ,
statusCode ,
null ,
mimeType ,
cgiBody . length ( ) ,
targetDate ,
null ,
null ,
null ,
null ,
nocache ) ;
out . write ( UTF8 . getBytes ( cgiBody ) ) ;
} else {
HTTPDemon . sendRespondError (
conProp ,
out ,
exitValue ,
statusCode ,
null ,
HeaderFramework . http1_1 . get (
Integer . toString ( statusCode ) ) ,
null ) ;
Log . logWarning (
" HTTPD " ,
" CGI script " + targetFile . getPath ( )
+ " returned exit value " + exitValue
+ " , body empty: "
+ ( cgiBody = = null | | cgiBody . isEmpty ( ) ) ) ;
if ( error . length ( ) > 0 ) {
Log . logWarning ( " HTTPD " , " Reported error: " + error ) ;
}
}
2009-07-19 22:37:44 +02:00
}
} else if ( ( targetClass ! = null ) & & ( path . endsWith ( " .stream " ) ) ) {
// call rewrite-class
2011-05-26 18:34:35 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_CLIENTIP , ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_CLIENTIP ) ) ;
2009-07-19 22:37:44 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_PATH , path ) ;
2010-05-11 13:14:05 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_EXT , " stream " ) ;
2009-07-19 22:37:44 +02:00
//requestHeader.put(httpHeader.CONNECTION_PROP_INPUTSTREAM, body);
//requestHeader.put(httpHeader.CONNECTION_PROP_OUTPUTSTREAM, out);
2011-06-21 00:53:43 +02:00
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 200 , null ) ;
2009-07-19 22:37:44 +02:00
// in case that there are no args given, args = null or empty hashmap
/* servletProperties tp = (servlerObjects) */ invokeServlet ( targetClass , requestHeader , args ) ;
forceConnectionClose ( conProp ) ;
2011-06-21 00:53:43 +02:00
return ;
2009-07-19 22:37:44 +02:00
} else if ( targetFile . exists ( ) & & targetFile . isFile ( ) & & targetFile . canRead ( ) ) {
// we have found a file that can be written to the client
// if this file uses templates, then we use the template
// re-write - method to create an result
2011-09-01 18:05:00 +02:00
String mimeType = Classification . ext2mime ( targetExt , " text/html " ) ;
2011-05-26 18:34:35 +02:00
String ext = ( String ) conProp . get ( " EXT " ) ; if ( ext = = null ) ext = " " ;
final boolean zipContent = requestHeader . acceptGzip ( ) & & HTTPDemon . shallTransportZipped ( " . " + ext ) ;
2011-06-21 00:53:43 +02:00
if ( path . endsWith ( " html " ) | |
path . endsWith ( " htm " ) | |
path . endsWith ( " xml " ) | |
path . endsWith ( " json " ) | |
path . endsWith ( " rdf " ) | |
path . endsWith ( " rss " ) | |
2009-07-19 22:37:44 +02:00
path . endsWith ( " csv " ) | |
path . endsWith ( " pac " ) | |
path . endsWith ( " src " ) | |
path . endsWith ( " vcf " ) | |
2010-05-11 13:14:05 +02:00
path . endsWith ( " kml " ) | |
2010-05-12 10:49:20 +02:00
path . endsWith ( " gpx " ) | |
2010-10-11 02:00:10 +02:00
path . endsWith ( " css " ) | |
2009-07-19 22:37:44 +02:00
path . endsWith ( " / " ) | |
path . equals ( " /robots.txt " ) ) {
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
/ * targetFile = getLocalizedFile ( path ) ;
if ( ! ( targetFile . exists ( ) ) ) {
// try to find that file in the htDocsPath
File trialFile = new File ( htDocsPath , path ) ;
if ( trialFile . exists ( ) ) targetFile = trialFile ;
} * /
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// call rewrite-class
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( targetClass ! = null ) {
// CGI-class: call the class to create a property for rewriting
try {
2011-05-26 18:34:35 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_CLIENTIP , ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_CLIENTIP ) ) ;
2009-07-19 22:37:44 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_PATH , path ) ;
2011-06-21 00:53:43 +02:00
final int ep = path . lastIndexOf ( " . " ) ;
2010-05-11 13:14:05 +02:00
requestHeader . put ( HeaderFramework . CONNECTION_PROP_EXT , path . substring ( ep + 1 ) ) ;
2009-07-19 22:37:44 +02:00
// in case that there are no args given, args = null or empty hashmap
final Object tmp = invokeServlet ( targetClass , requestHeader , args ) ;
if ( tmp = = null ) {
// if no args given, then tp will be an empty Hashtable object (not null)
templatePatterns = new servletProperties ( ) ;
} else if ( tmp instanceof servletProperties ) {
templatePatterns = ( servletProperties ) tmp ;
} else {
templatePatterns = new servletProperties ( ( serverObjects ) tmp ) ;
}
2010-05-11 13:14:05 +02:00
// check if the servlets requests authentication
2009-07-19 22:37:44 +02:00
if ( templatePatterns . containsKey ( servletProperties . ACTION_AUTHENTICATE ) ) {
// handle brute-force protection
2011-05-05 02:25:14 +02:00
if ( realmProp ! = null ) {
2009-07-19 22:37:44 +02:00
Log . logInfo ( " HTTPD " , " dynamic log-in for account 'admin' in http file handler for path ' " + path + " ' from host ' " + clientIP + " ' " ) ;
final Integer attempts = serverCore . bfHost . get ( clientIP ) ;
if ( attempts = = null )
serverCore . bfHost . put ( clientIP , Integer . valueOf ( 1 ) ) ;
else
serverCore . bfHost . put ( clientIP , Integer . valueOf ( attempts . intValue ( ) + 1 ) ) ;
}
// send authentication request to browser
final ResponseHeader headers = getDefaultHeaders ( path ) ;
headers . put ( RequestHeader . WWW_AUTHENTICATE , " Basic realm= \" " + templatePatterns . get ( servletProperties . ACTION_AUTHENTICATE , " " ) + " \" " ) ;
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 401 , headers ) ;
return ;
} else if ( templatePatterns . containsKey ( servletProperties . ACTION_LOCATION ) ) {
String location = templatePatterns . get ( servletProperties . ACTION_LOCATION , " " ) ;
if ( location . length ( ) = = 0 ) location = path ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
final ResponseHeader headers = getDefaultHeaders ( path ) ;
2010-11-02 17:28:40 +01:00
headers . setAdditionalHeaderProperties ( templatePatterns . getOutgoingHeader ( ) . getAdditionalHeaderProperties ( ) ) ; //put the cookies into the new header TODO: can we put all headerlines, without trouble?
2009-07-19 22:37:44 +02:00
headers . put ( HeaderFramework . LOCATION , location ) ;
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , 302 , headers ) ;
return ;
}
// add the application version, the uptime and the client name to every rewrite table
templatePatterns . put ( servletProperties . PEER_STAT_VERSION , yacyBuildProperties . getVersion ( ) ) ;
templatePatterns . put ( servletProperties . PEER_STAT_UPTIME , ( ( System . currentTimeMillis ( ) - serverCore . startupTime ) / 1000 ) / 60 ) ; // uptime in minutes
2011-01-28 11:12:17 +01:00
templatePatterns . putHTML ( servletProperties . PEER_STAT_CLIENTNAME , sb . peers . mySeed ( ) . getName ( ) ) ;
2009-07-19 22:37:44 +02:00
templatePatterns . putHTML ( servletProperties . PEER_STAT_CLIENTID , ( ( Switchboard ) switchboard ) . peers . myID ( ) ) ;
2011-02-25 22:11:53 +01:00
templatePatterns . put ( servletProperties . PEER_STAT_MYTIME , GenericFormatter . SHORT_SECOND_FORMATTER . format ( ) ) ;
2011-10-04 11:06:24 +02:00
final Seed myPeer = sb . peers . mySeed ( ) ;
2011-06-21 00:53:43 +02:00
templatePatterns . put ( " newpeer " , myPeer . getAge ( ) > = 1 ? 0 : 1 ) ;
2010-09-17 12:20:04 +02:00
templatePatterns . putHTML ( " newpeer_peerhash " , myPeer . hash ) ;
2009-07-19 22:37:44 +02:00
//System.out.println("respond props: " + ((tp == null) ? "null" : tp.toString())); // debug
} catch ( final InvocationTargetException e ) {
if ( e . getCause ( ) instanceof InterruptedException ) {
throw new InterruptedException ( e . getCause ( ) . getMessage ( ) ) ;
2011-06-21 00:53:43 +02:00
}
2012-05-30 15:27:45 +02:00
Log . logException ( e ) ;
Log . logException ( e . getCause ( ) ) ;
Log . logException ( e . getTargetException ( ) ) ;
2009-07-19 22:37:44 +02:00
targetClass = null ;
throw e ;
}
nocache = true ;
}
targetDate = new Date ( targetFile . lastModified ( ) ) ;
2011-07-19 13:47:53 +02:00
Date expireDate = null ;
if ( templatePatterns = = null ) {
// if the file will not be changed, cache it in the browser
expireDate = new Date ( new Date ( ) . getTime ( ) + ( 31l * 24 * 60 * 60 * 1000 ) ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// rewrite the file
InputStream fis = null ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// read the file/template
TemplateCacheEntry templateCacheEntry = null ;
2011-06-21 00:53:43 +02:00
final long fileSize = targetFile . length ( ) ;
2009-07-19 22:37:44 +02:00
if ( useTemplateCache & & fileSize < = 512 * 1024 ) {
// read from cache
SoftReference < TemplateCacheEntry > ref = templateCache . get ( targetFile ) ;
if ( ref ! = null ) {
templateCacheEntry = ref . get ( ) ;
if ( templateCacheEntry = = null ) templateCache . remove ( targetFile ) ;
}
2011-06-21 00:53:43 +02:00
final Date targetFileDate = new Date ( targetFile . lastModified ( ) ) ;
2009-07-19 22:37:44 +02:00
if ( templateCacheEntry = = null | | targetFileDate . after ( templateCacheEntry . lastModified ) ) {
// loading the content of the template file into
// a byte array
2011-07-19 13:47:53 +02:00
templateCacheEntry = new TemplateCacheEntry ( ) ;
2009-07-19 22:37:44 +02:00
templateCacheEntry . lastModified = targetFileDate ;
templateCacheEntry . content = FileUtils . read ( targetFile ) ;
// storing the content into the cache
ref = new SoftReference < TemplateCacheEntry > ( templateCacheEntry ) ;
2011-03-09 17:32:34 +01:00
if ( MemoryControl . shortStatus ( ) ) templateCache . clear ( ) ;
2009-07-19 22:37:44 +02:00
templateCache . put ( targetFile , ref ) ;
if ( theLogger . isFinest ( ) ) theLogger . logFinest ( " Cache MISS for file " + targetFile ) ;
} else {
if ( theLogger . isFinest ( ) ) theLogger . logFinest ( " Cache HIT for file " + targetFile ) ;
}
// creating an inputstream needed by the template
// rewrite function
fis = new ByteArrayInputStream ( templateCacheEntry . content ) ;
templateCacheEntry = null ;
} else if ( fileSize < = Math . min ( 4 * 1024 * 1204 , MemoryControl . available ( ) / 100 ) ) {
// read file completely into ram, avoid that too many files are open at the same time
fis = new ByteArrayInputStream ( FileUtils . read ( targetFile ) ) ;
} else {
fis = new BufferedInputStream ( new FileInputStream ( targetFile ) ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( mimeType . startsWith ( " text " ) ) {
// every text-file distributed by yacy is UTF-8
2012-02-02 07:37:00 +01:00
if ( ! path . startsWith ( " /repository " ) ) {
2009-07-19 22:37:44 +02:00
mimeType = mimeType + " ; charset=UTF-8 " ;
} else {
// detect charset of html-files
2012-02-02 07:37:00 +01:00
if ( ( path . endsWith ( " html " ) | | path . endsWith ( " htm " ) ) ) {
2009-07-19 22:37:44 +02:00
// save position
fis . mark ( 1000 ) ;
// scrape document to look up charset
2012-02-02 07:37:00 +01:00
final ScraperInputStream htmlFilter = new ScraperInputStream ( fis , " UTF-8 " , new DigestURI ( " http://localhost " ) , null , false ) ;
2009-07-19 22:37:44 +02:00
final String charset = htmlParser . patchCharsetEncoding ( htmlFilter . detectCharset ( ) ) ;
2012-02-02 07:37:00 +01:00
htmlFilter . close ( ) ;
if ( charset ! = null ) mimeType = mimeType + " ; charset= " + charset ;
2009-07-19 22:37:44 +02:00
// reset position
fis . reset ( ) ;
}
}
}
// write the array to the client
// we can do that either in standard mode (whole thing completely) or in chunked mode
// since yacy clients do not understand chunked mode (yet), we use this only for communication with the administrator
final boolean yacyClient = requestHeader . userAgent ( ) . startsWith ( " yacy " ) ;
final boolean chunked = ! method . equals ( HeaderFramework . METHOD_HEAD ) & & ! yacyClient & & httpVersion . equals ( HeaderFramework . HTTP_VERSION_1_1 ) ;
if ( chunked ) {
// send page in chunks and parse SSIs
final ByteBuffer o = new ByteBuffer ( ) ;
// apply templates
2011-11-30 21:50:41 +01:00
TemplateEngine . writeTemplate ( fis , o , templatePatterns , UNRESOLVED_PATTERN ) ;
2009-07-19 22:37:44 +02:00
fis . close ( ) ;
2010-11-27 10:16:16 +01:00
HTTPDemon . sendRespondHeader ( conProp , out ,
httpVersion , 200 , null , mimeType , - 1 ,
2011-07-19 13:47:53 +02:00
targetDate , expireDate , ( templatePatterns = = null ) ? new ResponseHeader ( ) : templatePatterns . getOutgoingHeader ( ) ,
2011-07-19 14:04:40 +02:00
null , " chunked " , nocache ) ;
2009-07-19 22:37:44 +02:00
// send the content in chunked parts, see RFC 2616 section 3.6.1
2011-07-19 14:04:40 +02:00
final ChunkedOutputStream chos = new ChunkedOutputStream ( out ) ;
// GZIPOutputStream does not implement flush (this is a bug IMHO)
// so we can't compress this stuff, without loosing the cool SSI trickle feature
2011-09-20 16:54:14 +02:00
ServerSideIncludes . writeSSI ( o , chos , realmProp , clientIP , requestHeader ) ;
2011-07-19 14:04:40 +02:00
//chos.write(result);
chos . finish ( ) ;
} else {
2009-07-19 22:37:44 +02:00
// send page as whole thing, SSIs are not possible
2011-07-19 14:04:40 +02:00
final String contentEncoding = ( zipContent ) ? " gzip " : null ;
2009-07-19 22:37:44 +02:00
// apply templates
final ByteBuffer o1 = new ByteBuffer ( ) ;
2011-05-27 10:24:54 +02:00
TemplateEngine . writeTemplate ( fis , o1 , templatePatterns , ASCII . getBytes ( " -UNRESOLVED_PATTERN- " ) ) ;
2009-07-19 22:37:44 +02:00
fis . close ( ) ;
final ByteBuffer o = new ByteBuffer ( ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( zipContent ) {
GZIPOutputStream zippedOut = new GZIPOutputStream ( o ) ;
2011-09-20 16:54:14 +02:00
ServerSideIncludes . writeSSI ( o1 , zippedOut , realmProp , clientIP , requestHeader ) ;
2009-07-19 22:37:44 +02:00
//httpTemplate.writeTemplate(fis, zippedOut, tp, "-UNRESOLVED_PATTERN-".getBytes("UTF-8"));
zippedOut . finish ( ) ;
zippedOut . flush ( ) ;
zippedOut . close ( ) ;
zippedOut = null ;
} else {
2011-09-20 16:54:14 +02:00
ServerSideIncludes . writeSSI ( o1 , o , realmProp , clientIP , requestHeader ) ;
2009-07-19 22:37:44 +02:00
//httpTemplate.writeTemplate(fis, o, tp, "-UNRESOLVED_PATTERN-".getBytes("UTF-8"));
}
if ( method . equals ( HeaderFramework . METHOD_HEAD ) ) {
HTTPDemon . sendRespondHeader ( conProp , out ,
httpVersion , 200 , null , mimeType , o . length ( ) ,
2011-07-19 13:47:53 +02:00
targetDate , expireDate , ( templatePatterns = = null ) ? new ResponseHeader ( ) : templatePatterns . getOutgoingHeader ( ) ,
2009-07-19 22:37:44 +02:00
contentEncoding , null , nocache ) ;
} else {
final byte [ ] result = o . getBytes ( ) ; // this interrupts streaming (bad idea!)
HTTPDemon . sendRespondHeader ( conProp , out ,
httpVersion , 200 , null , mimeType , result . length ,
2011-07-19 13:47:53 +02:00
targetDate , expireDate , ( templatePatterns = = null ) ? new ResponseHeader ( ) : templatePatterns . getOutgoingHeader ( ) ,
2009-07-19 22:37:44 +02:00
contentEncoding , null , nocache ) ;
FileUtils . copy ( result , out ) ;
2011-06-21 00:53:43 +02:00
}
2009-07-19 22:37:44 +02:00
}
} else { // no html
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
int statusCode = 200 ;
int rangeStartOffset = 0 ;
2011-06-21 00:53:43 +02:00
final ResponseHeader header = new ResponseHeader ( ) ;
2009-07-19 22:37:44 +02:00
// adding the accept ranges header
header . put ( HeaderFramework . ACCEPT_RANGES , " bytes " ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// reading the files md5 hash if availabe and use it as ETAG of the resource
String targetMD5 = null ;
final File targetMd5File = new File ( targetFile + " .md5 " ) ;
try {
if ( targetMd5File . exists ( ) ) {
//String description = null;
2011-03-07 21:36:40 +01:00
targetMD5 = UTF8 . String ( FileUtils . read ( targetMd5File ) ) ;
2011-06-21 00:53:43 +02:00
final int pos = targetMD5 . indexOf ( '\n' ) ;
2009-07-19 22:37:44 +02:00
if ( pos > = 0 ) {
//description = targetMD5.substring(pos + 1);
targetMD5 = targetMD5 . substring ( 0 , pos ) ;
}
// using the checksum as ETAG header
header . put ( HeaderFramework . ETAG , targetMD5 ) ;
}
} catch ( final IOException e ) {
2009-11-05 21:28:37 +01:00
Log . logException ( e ) ;
2011-06-21 00:53:43 +02:00
}
2009-07-19 22:37:44 +02:00
if ( requestHeader . containsKey ( HeaderFramework . RANGE ) ) {
final Object ifRange = requestHeader . ifRange ( ) ;
if ( ( ifRange = = null ) | |
( ifRange instanceof Date & & targetFile . lastModified ( ) = = ( ( Date ) ifRange ) . getTime ( ) ) | |
( ifRange instanceof String & & ifRange . equals ( targetMD5 ) ) ) {
final String rangeHeaderVal = requestHeader . get ( HeaderFramework . RANGE ) . trim ( ) ;
if ( rangeHeaderVal . startsWith ( " bytes= " ) ) {
final String rangesVal = rangeHeaderVal . substring ( " bytes= " . length ( ) ) ;
final String [ ] ranges = rangesVal . split ( " , " ) ;
if ( ( ranges . length = = 1 ) & & ( ranges [ 0 ] . endsWith ( " - " ) ) ) {
2012-05-21 17:52:30 +02:00
rangeStartOffset = NumberTools . parseIntDecSubstring ( ranges [ 0 ] , 0 , ranges [ 0 ] . length ( ) - 1 ) ;
2009-07-19 22:37:44 +02:00
statusCode = 206 ;
header . put ( HeaderFramework . CONTENT_RANGE , " bytes " + rangeStartOffset + " - " + ( targetFile . length ( ) - 1 ) + " / " + targetFile . length ( ) ) ;
}
}
}
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
// write the file to the client
targetDate = new Date ( targetFile . lastModified ( ) ) ;
2011-07-19 13:47:53 +02:00
// cache file for one month in browser (but most browsers won't cache for that long)
final Date expireDate = new Date ( new Date ( ) . getTime ( ) + ( 31l * 24 * 60 * 60 * 1000 ) ) ;
2009-07-19 22:37:44 +02:00
final long contentLength = ( zipContent ) ? - 1 : targetFile . length ( ) - rangeStartOffset ;
final String contentEncoding = ( zipContent ) ? " gzip " : null ;
final String transferEncoding = ( httpVersion . equals ( HeaderFramework . HTTP_VERSION_1_1 ) & & zipContent ) ? " chunked " : null ;
if ( ! httpVersion . equals ( HeaderFramework . HTTP_VERSION_1_1 ) & & zipContent ) forceConnectionClose ( conProp ) ;
2011-06-21 00:53:43 +02:00
2011-07-19 13:47:53 +02:00
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , statusCode , null , mimeType , contentLength , targetDate , expireDate , header , contentEncoding , transferEncoding , nocache ) ;
2011-06-21 00:53:43 +02:00
if ( ! method . equals ( HeaderFramework . METHOD_HEAD ) ) {
2009-07-19 22:37:44 +02:00
ChunkedOutputStream chunkedOut = null ;
GZIPOutputStream zipped = null ;
OutputStream newOut = out ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( transferEncoding ! = null ) {
chunkedOut = new ChunkedOutputStream ( newOut ) ;
newOut = chunkedOut ;
}
if ( contentEncoding ! = null ) {
zipped = new GZIPOutputStream ( newOut ) ;
newOut = zipped ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
FileUtils . copyRange ( targetFile , newOut , rangeStartOffset ) ;
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
if ( zipped ! = null ) {
zipped . flush ( ) ;
zipped . finish ( ) ;
}
if ( chunkedOut ! = null ) {
chunkedOut . finish ( ) ;
}
// flush all
try { newOut . flush ( ) ; } catch ( final Exception e ) { }
}
}
} else {
HTTPDemon . sendRespondError ( conProp , out , 3 , 404 , " File not Found " , null , null ) ;
return ;
}
2011-05-26 18:34:35 +02:00
} catch ( final Exception e ) {
2009-07-19 22:37:44 +02:00
try {
2010-09-14 23:03:50 +02:00
// error handling
2011-06-21 00:53:43 +02:00
int httpStatusCode = 400 ;
final String httpStatusText = null ;
final StringBuilder errorMessage = new StringBuilder ( 2000 ) ;
Exception errorExc = null ;
2009-07-19 22:37:44 +02:00
final String errorMsg = e . getMessage ( ) ;
if (
( e instanceof InterruptedException ) | |
( ( errorMsg ! = null ) & & ( errorMsg . startsWith ( " Socket closed " ) ) & & ( Thread . currentThread ( ) . isInterrupted ( ) ) )
) {
errorMessage . append ( " Interruption detected while processing query. " ) ;
httpStatusCode = 503 ;
} else {
2011-06-21 00:53:43 +02:00
if ( ( errorMsg ! = null ) & &
2009-07-19 22:37:44 +02:00
(
2009-11-05 17:40:15 +01:00
errorMsg . contains ( " Broken pipe " ) | |
2009-07-19 22:37:44 +02:00
errorMsg . contains ( " Connection reset " ) | |
2009-11-05 17:40:15 +01:00
errorMsg . contains ( " Read timed out " ) | |
errorMsg . contains ( " Connection timed out " ) | |
2011-06-21 00:53:43 +02:00
errorMsg . contains ( " Software caused connection abort " )
2009-07-19 22:37:44 +02:00
) ) {
// client closed the connection, so we just end silently
errorMessage . append ( " Client unexpectedly closed connection while processing query. " ) ;
} else {
errorMessage . append ( " Unexpected error while processing query. " ) ;
httpStatusCode = 500 ;
errorExc = e ;
}
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
errorMessage . append ( " \ nSession: " ) . append ( Thread . currentThread ( ) . getName ( ) )
. append ( " \ nQuery: " ) . append ( path )
2011-06-21 00:53:43 +02:00
. append ( " \ nClient: " ) . append ( conProp . get ( HeaderFramework . CONNECTION_PROP_CLIENTIP ) )
. append ( " \ nReason: " ) . append ( e . getMessage ( ) ) ;
2009-07-19 22:37:44 +02:00
if ( ! conProp . containsKey ( HeaderFramework . CONNECTION_PROP_PROXY_RESPOND_HEADER ) ) {
2011-06-21 00:53:43 +02:00
// sending back an error message to the client
2009-07-19 22:37:44 +02:00
// if we have not already send an http header
2011-03-07 21:36:40 +01:00
HTTPDemon . sendRespondError ( conProp , out , 4 , httpStatusCode , httpStatusText , errorMessage . toString ( ) , errorExc ) ;
2009-07-19 22:37:44 +02:00
} else {
// otherwise we close the connection
forceConnectionClose ( conProp ) ;
2011-06-21 00:53:43 +02:00
}
// if it is an unexpected error we log it
2009-07-19 22:37:44 +02:00
if ( httpStatusCode = = 500 ) {
2011-03-07 21:36:40 +01:00
theLogger . logWarning ( errorMessage . toString ( ) , e ) ;
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
} catch ( final Exception ee ) {
forceConnectionClose ( conProp ) ;
2011-06-21 00:53:43 +02:00
}
2009-07-19 22:37:44 +02:00
} finally {
try { out . flush ( ) ; } catch ( final Exception e ) { }
}
}
2011-06-21 00:53:43 +02:00
2011-12-04 14:43:35 +01:00
/ * *
* Returns a list which contains parts of command
* which is used to start external process for
* CGI scripts .
* @param targetFile file to run
* @return list of parts of command
* @throws IOException if file can not be accessed
* /
private static List < String > assembleCommandFromShebang (
final File targetFile )
throws IOException {
final List < String > ret = new ArrayList < String > ( ) ;
final BufferedReader br =
new BufferedReader ( new FileReader ( targetFile ) , 512 ) ;
final String line = br . readLine ( ) ;
if ( line . startsWith ( " #! " ) ) {
ret . addAll ( Arrays . asList ( line . substring ( 2 ) . split ( " " ) ) ) ;
}
ret . add ( targetFile . getAbsolutePath ( ) ) ;
return ret ;
}
2011-06-21 00:53:43 +02:00
private static final String appendPath ( final String proplist , final String path ) {
2011-01-27 12:17:11 +01:00
if ( proplist . length ( ) = = 0 ) return path ;
if ( proplist . indexOf ( path ) > = 0 ) return proplist ;
return proplist + " , " + path ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
public static final File getOverlayedClass ( final String path ) {
File targetClass ;
targetClass = rewriteClassFile ( new File ( htDefaultPath , path ) ) ; //works for default and localized files
if ( targetClass = = null | | ! targetClass . exists ( ) ) {
//works for htdocs
targetClass = rewriteClassFile ( new File ( htDocsPath , path ) ) ;
}
return targetClass ;
}
public static final File getOverlayedFile ( final String path ) {
File targetFile ;
targetFile = getLocalizedFile ( path ) ;
if ( ! targetFile . exists ( ) ) {
targetFile = new File ( htDocsPath , path ) ;
}
return targetFile ;
}
2011-06-21 00:53:43 +02:00
2011-05-26 18:34:35 +02:00
private static final void forceConnectionClose ( final HashMap < String , Object > conprop ) {
2009-07-19 22:37:44 +02:00
if ( conprop ! = null ) {
2011-06-21 00:53:43 +02:00
conprop . put ( HeaderFramework . CONNECTION_PROP_PERSISTENT , " close " ) ;
2009-07-19 22:37:44 +02:00
}
}
private static final File rewriteClassFile ( final File template ) {
try {
String f = template . getCanonicalPath ( ) ;
2010-01-13 01:23:07 +01:00
final int p = f . lastIndexOf ( '.' ) ;
2009-07-19 22:37:44 +02:00
if ( p < 0 ) return null ;
f = f . substring ( 0 , p ) + " .class " ;
//System.out.println("constructed class path " + f);
final File cf = new File ( f ) ;
if ( cf . exists ( ) ) return cf ;
return null ;
} catch ( final IOException e ) {
return null ;
}
}
2011-06-21 00:53:43 +02:00
private static final Method rewriteMethod ( final File classFile ) throws InvocationTargetException {
2009-07-19 22:37:44 +02:00
Method m = null ;
// now make a class out of the stream
try {
2011-06-21 00:53:43 +02:00
if ( templateMethodCache ! = null ) {
2009-07-19 22:37:44 +02:00
final SoftReference < Method > ref = templateMethodCache . get ( classFile ) ;
if ( ref ! = null ) {
m = ref . get ( ) ;
if ( m = = null ) {
templateMethodCache . remove ( classFile ) ;
} else {
return m ;
}
2011-05-24 11:31:07 +02:00
}
2009-07-19 22:37:44 +02:00
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
final Class < ? > c = provider . loadClass ( classFile ) ;
final Class < ? > [ ] params = new Class [ ] {
RequestHeader . class ,
serverObjects . class ,
serverSwitch . class } ;
m = c . getMethod ( " respond " , params ) ;
2011-06-21 00:53:43 +02:00
2011-05-24 11:31:07 +02:00
if ( MemoryControl . shortStatus ( ) ) {
templateMethodCache . clear ( ) ;
} else {
2009-07-19 22:37:44 +02:00
// storing the method into the cache
final SoftReference < Method > ref = new SoftReference < Method > ( m ) ;
templateMethodCache . put ( classFile , ref ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
} catch ( final ClassNotFoundException e ) {
2010-09-20 13:01:44 +02:00
Log . logSevere ( " HTTPDFileHandler " , " class " + classFile + " is missing: " + e . getMessage ( ) ) ;
throw new InvocationTargetException ( e , " class " + classFile + " is missing: " + e . getMessage ( ) ) ;
2009-07-19 22:37:44 +02:00
} catch ( final NoSuchMethodException e ) {
2010-09-20 13:01:44 +02:00
Log . logSevere ( " HTTPDFileHandler " , " method 'respond' not found in class " + classFile + " : " + e . getMessage ( ) ) ;
throw new InvocationTargetException ( e , " method 'respond' not found in class " + classFile + " : " + e . getMessage ( ) ) ;
2009-07-19 22:37:44 +02:00
}
return m ;
}
2011-06-21 00:53:43 +02:00
2010-10-04 13:54:48 +02:00
private static final Object invokeServlet ( final File targetClass , final RequestHeader request , final serverObjects args ) throws IllegalArgumentException , IllegalAccessException , InvocationTargetException {
2011-03-07 02:45:11 +01:00
try {
return rewriteMethod ( targetClass ) . invoke ( null , new Object [ ] { request , args , switchboard } ) ;
2011-06-21 00:53:43 +02:00
} catch ( final OutOfMemoryError e ) {
2011-03-07 02:45:11 +01:00
Log . logException ( e ) ;
return null ;
}
2009-07-19 22:37:44 +02:00
}
/ * *
* Tells if a filename ends with a suffix from a given list .
* @param filename the filename
* @param suffixList the list of suffixes which is a string of suffixes separated by commas
* @return true if the filename ends with a suffix from the list , else false
* /
2011-06-21 00:53:43 +02:00
private static boolean matchesSuffix ( final String name , final String suffixList ) {
2009-07-19 22:37:44 +02:00
boolean ret = false ;
if ( suffixList ! = null & & name ! = null ) {
2011-06-21 00:53:43 +02:00
final String [ ] suffixes = suffixList . split ( " , " ) ;
2009-07-19 22:37:44 +02:00
find :
for ( int i = 0 ; i < suffixes . length ; i + + ) {
if ( name . endsWith ( " . " + suffixes [ i ] . trim ( ) ) ) {
ret = true ;
break find ;
}
}
}
return ret ;
}
2011-06-21 00:53:43 +02:00
2011-02-27 22:39:38 +01:00
/ * *
* do a proxy request for document
* extracts url from GET - parameter url
* not in separete servlet , because we need access to binary outstream
2011-06-21 00:53:43 +02:00
* @throws IOException
2011-02-27 22:39:38 +01:00
* /
2011-11-08 13:02:22 +01:00
private static void doURLProxy ( final serverObjects args , final HashMap < String , Object > conProp , final RequestHeader requestHeader , final OutputStream out ) throws IOException {
2011-05-26 18:34:35 +02:00
final String httpVersion = ( String ) conProp . get ( HeaderFramework . CONNECTION_PROP_HTTP_VER ) ;
2011-02-27 22:39:38 +01:00
URL proxyurl = null ;
2011-06-21 00:53:43 +02:00
2011-05-30 23:25:20 +02:00
if ( conProp ! = null & & conProp . containsKey ( " ARGS " ) ) {
2011-06-21 00:53:43 +02:00
final String strARGS = ( String ) conProp . get ( " ARGS " ) ;
2011-05-30 23:25:20 +02:00
if ( strARGS . startsWith ( " url= " ) ) {
2011-06-21 00:53:43 +02:00
final String strUrl = strARGS . substring ( 4 ) ; // strip url=
2012-06-10 23:17:21 +02:00
2012-02-24 17:02:57 +01:00
try {
2011-05-30 23:25:20 +02:00
proxyurl = new URL ( strUrl ) ;
2012-02-24 17:02:57 +01:00
} catch ( MalformedURLException e ) {
2012-06-10 23:17:21 +02:00
proxyurl = new URL ( URLDecoder . decode ( strUrl , UTF8 . charset . name ( ) ) ) ;
2012-02-24 17:02:57 +01:00
}
2011-05-30 23:25:20 +02:00
}
2011-02-27 22:39:38 +01:00
}
2011-06-21 00:53:43 +02:00
2011-05-30 23:25:20 +02:00
if ( proxyurl = = null ) {
throw new IOException ( " no url as argument supplied " ) ;
}
2011-09-20 17:02:15 +02:00
String host = proxyurl . getHost ( ) ;
if ( proxyurl . getPort ( ) ! = - 1 ) {
2011-11-08 13:02:22 +01:00
host + = " : " + proxyurl . getPort ( ) ;
2011-09-20 17:02:15 +02:00
}
2011-05-30 23:25:20 +02:00
2011-02-27 22:39:38 +01:00
// set properties for proxy connection
2011-05-26 18:34:35 +02:00
final HashMap < String , Object > prop = new HashMap < String , Object > ( ) ;
prop . put ( HeaderFramework . CONNECTION_PROP_HTTP_VER , HeaderFramework . HTTP_VERSION_1_1 ) ;
2011-09-20 17:02:15 +02:00
prop . put ( HeaderFramework . CONNECTION_PROP_HOST , host ) ;
2011-05-30 23:25:20 +02:00
prop . put ( HeaderFramework . CONNECTION_PROP_PATH , proxyurl . getFile ( ) . replaceAll ( " " , " %20 " ) ) ;
2011-05-26 18:34:35 +02:00
prop . put ( HeaderFramework . CONNECTION_PROP_REQUESTLINE , " PROXY " ) ;
prop . put ( " CLIENTIP " , " 0:0:0:0:0:0:0:1 " ) ;
2011-02-27 22:39:38 +01:00
// remove some stuff from request header, so it isn't send to the server
requestHeader . remove ( " CLIENTIP " ) ;
requestHeader . remove ( " EXT " ) ;
requestHeader . remove ( " PATH " ) ;
requestHeader . remove ( " Authorization " ) ;
requestHeader . remove ( " Connection " ) ;
requestHeader . put ( HeaderFramework . HOST , proxyurl . getHost ( ) ) ;
2011-06-21 00:53:43 +02:00
final ByteArrayOutputStream o = new ByteArrayOutputStream ( ) ;
2011-02-27 22:39:38 +01:00
HTTPDProxyHandler . doGet ( prop , requestHeader , o ) ;
// reparse header to extract content-length and mimetype
final ResponseHeader outgoingHeader = new ResponseHeader ( ) ;
2011-06-21 00:53:43 +02:00
final InputStream in = new ByteArrayInputStream ( o . toByteArray ( ) ) ;
2011-02-27 22:39:38 +01:00
String line = readLine ( in ) ;
while ( line ! = null & & ! line . equals ( " " ) ) {
int p ;
if ( ( p = line . indexOf ( ':' ) ) > = 0 ) {
// store a property
outgoingHeader . add ( line . substring ( 0 , p ) . trim ( ) , line . substring ( p + 1 ) . trim ( ) ) ;
}
line = readLine ( in ) ;
}
if ( line = = null ) {
HTTPDemon . sendRespondError ( conProp , out , 3 , 500 , " null " , null , null ) ;
return ;
}
2011-06-21 00:53:43 +02:00
2011-05-30 23:25:20 +02:00
final int httpStatus = Integer . parseInt ( ( String ) prop . get ( HeaderFramework . CONNECTION_PROP_PROXY_RESPOND_STATUS ) ) ;
2011-06-21 00:53:43 +02:00
2011-05-30 23:25:20 +02:00
String directory = " " ;
if ( proxyurl . getPath ( ) . lastIndexOf ( '/' ) > 0 )
directory = proxyurl . getPath ( ) . substring ( 0 , proxyurl . getPath ( ) . lastIndexOf ( '/' ) ) ;
2012-06-10 23:17:21 +02:00
2012-02-14 15:51:20 +01:00
String location = " " ;
2011-05-30 23:25:20 +02:00
if ( outgoingHeader . containsKey ( " Location " ) ) {
// rewrite location header
2012-02-14 15:51:20 +01:00
location = outgoingHeader . get ( " Location " ) ;
2011-05-30 23:25:20 +02:00
if ( location . startsWith ( " http " ) ) {
location = " /proxy.html?url= " + location ;
} else {
location = " /proxy.html?url=http:// " + proxyurl . getHost ( ) + " / " + location ;
}
outgoingHeader . put ( " Location " , location ) ;
}
2011-06-21 00:53:43 +02:00
final String mimeType = outgoingHeader . getContentType ( ) ;
2012-02-14 15:51:20 +01:00
if ( ( mimeType . startsWith ( " text/html " ) | | mimeType . startsWith ( " text " ) ) ) {
2011-06-21 00:53:43 +02:00
final StringWriter buffer = new StringWriter ( ) ;
2011-02-27 22:39:38 +01:00
if ( outgoingHeader . containsKey ( HeaderFramework . TRANSFER_ENCODING ) ) {
2011-03-11 00:25:07 +01:00
FileUtils . copy ( new ChunkedInputStream ( in ) , buffer , UTF8 . charset ) ;
2011-02-27 22:39:38 +01:00
} else {
2011-03-11 00:25:07 +01:00
FileUtils . copy ( in , buffer , UTF8 . charset ) ;
2011-02-27 22:39:38 +01:00
}
2011-06-21 00:53:43 +02:00
final String sbuffer = buffer . toString ( ) ;
final Pattern p = Pattern . compile ( " (href= \" |src= \" )([^ \" ]+)|(href='|src=')([^']+)|(url \\ (')([^']+)|(url \\ ( \" )([^ \" ]+)|(url \\ ()([^ \\ )]+) " ) ;
final Matcher m = p . matcher ( sbuffer ) ;
2011-06-22 01:10:50 +02:00
final StringBuffer result = new StringBuffer ( 80 ) ;
2011-08-02 01:35:24 +02:00
String init , url ;
MultiProtocolURI target ;
2011-06-01 15:27:04 +02:00
while ( m . find ( ) ) {
2011-08-02 01:35:24 +02:00
init = null ;
2011-06-01 15:27:04 +02:00
if ( m . group ( 1 ) ! = null ) init = m . group ( 1 ) ;
if ( m . group ( 3 ) ! = null ) init = m . group ( 3 ) ;
if ( m . group ( 5 ) ! = null ) init = m . group ( 5 ) ;
if ( m . group ( 7 ) ! = null ) init = m . group ( 7 ) ;
if ( m . group ( 9 ) ! = null ) init = m . group ( 9 ) ;
2011-08-02 01:35:24 +02:00
url = null ;
2011-06-01 15:27:04 +02:00
if ( m . group ( 2 ) ! = null ) url = m . group ( 2 ) ;
if ( m . group ( 4 ) ! = null ) url = m . group ( 4 ) ;
if ( m . group ( 6 ) ! = null ) url = m . group ( 6 ) ;
if ( m . group ( 8 ) ! = null ) url = m . group ( 8 ) ;
if ( m . group ( 10 ) ! = null ) url = m . group ( 10 ) ;
2011-07-12 09:44:23 +02:00
if ( url . startsWith ( " data: " ) | | url . startsWith ( " # " ) | | url . startsWith ( " mailto: " ) | | url . startsWith ( " javascript: " ) ) {
2011-09-29 14:53:55 +02:00
String newurl = init + url ;
newurl = newurl . replaceAll ( " \\ $ " , " \\ \\ \\ $ " ) ;
m . appendReplacement ( result , newurl ) ;
2011-06-21 00:53:43 +02:00
2011-06-01 15:27:04 +02:00
} else if ( url . startsWith ( " http " ) ) {
// absoulte url of form href="http://domain.com/path"
if ( sb . getConfig ( " proxyURL.rewriteURLs " , " all " ) . equals ( " domainlist " ) ) {
if ( sb . crawlStacker . urlInAcceptedDomain ( new DigestURI ( url ) ) ! = null ) {
continue ;
}
}
2011-06-21 00:53:43 +02:00
2011-09-29 14:53:55 +02:00
String newurl = init + " /proxy.html?url= " + url ;
newurl = newurl . replaceAll ( " \\ $ " , " \\ \\ \\ $ " ) ;
m . appendReplacement ( result , newurl ) ;
} else if ( url . startsWith ( " // " ) ) {
// absoulte url but same protocol of form href="//domain.com/path"
2011-11-08 13:02:22 +01:00
final String complete_url = proxyurl . getProtocol ( ) + " : " + url ;
2011-09-29 14:53:55 +02:00
if ( sb . getConfig ( " proxyURL.rewriteURLs " , " all " ) . equals ( " domainlist " ) ) {
if ( sb . crawlStacker . urlInAcceptedDomain ( new DigestURI ( complete_url ) ) ! = null ) {
continue ;
}
}
String newurl = init + " /proxy.html?url= " + complete_url ;
newurl = newurl . replaceAll ( " \\ $ " , " \\ \\ \\ $ " ) ;
m . appendReplacement ( result , newurl ) ;
2011-06-21 00:53:43 +02:00
2011-06-01 15:27:04 +02:00
} else if ( url . startsWith ( " / " ) ) {
// absolute path of form href="/absolute/path/to/linked/page"
2011-09-29 14:53:55 +02:00
String newurl = init + " /proxy.html?url=http:// " + host + url ;
newurl = newurl . replaceAll ( " \\ $ " , " \\ \\ \\ $ " ) ;
m . appendReplacement ( result , newurl ) ;
2011-06-21 00:53:43 +02:00
2011-06-01 15:27:04 +02:00
} else {
// relative path of form href="relative/path"
2011-07-12 09:44:23 +02:00
try {
2011-09-29 14:53:55 +02:00
target = new MultiProtocolURI ( " http:// " + host + directory + " / " + url ) ;
String newurl = init + " /proxy.html?url= " + target . toString ( ) ;
newurl = newurl . replaceAll ( " \\ $ " , " \\ \\ \\ $ " ) ;
m . appendReplacement ( result , newurl ) ;
2011-07-12 09:44:23 +02:00
}
2011-11-08 13:02:22 +01:00
catch ( final MalformedURLException e ) { }
2011-06-21 00:53:43 +02:00
2011-06-01 15:27:04 +02:00
}
}
m . appendTail ( result ) ;
2011-06-21 00:53:43 +02:00
final byte [ ] sbb = UTF8 . getBytes ( result . toString ( ) ) ;
2011-02-27 22:39:38 +01:00
if ( outgoingHeader . containsKey ( HeaderFramework . TRANSFER_ENCODING ) ) {
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , httpStatus , outgoingHeader ) ;
2011-06-21 00:53:43 +02:00
2011-11-08 13:02:22 +01:00
final ChunkedOutputStream cos = new ChunkedOutputStream ( out ) ;
2011-06-29 11:32:02 +02:00
cos . write ( sbb ) ;
cos . finish ( ) ;
2011-02-27 22:39:38 +01:00
} else {
2011-04-12 07:02:36 +02:00
outgoingHeader . put ( HeaderFramework . CONTENT_LENGTH , Integer . toString ( sbb . length ) ) ;
2011-06-21 00:53:43 +02:00
2011-02-27 22:39:38 +01:00
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , httpStatus , outgoingHeader ) ;
2011-06-29 11:32:02 +02:00
out . write ( sbb ) ;
}
2011-02-27 22:39:38 +01:00
} else {
if ( ! outgoingHeader . containsKey ( HeaderFramework . CONTENT_LENGTH ) )
2011-05-26 18:34:35 +02:00
outgoingHeader . put ( HeaderFramework . CONTENT_LENGTH , ( String ) prop . get ( HeaderFramework . CONNECTION_PROP_PROXY_RESPOND_SIZE ) ) ;
2011-02-27 22:39:38 +01:00
HTTPDemon . sendRespondHeader ( conProp , out , httpVersion , httpStatus , outgoingHeader ) ;
FileUtils . copy ( in , out ) ;
}
return ;
}
2011-06-21 00:53:43 +02:00
private static String readLine ( final InputStream in ) throws IOException {
final ByteArrayOutputStream buf = new ByteArrayOutputStream ( ) ;
2011-02-27 22:39:38 +01:00
int b ;
while ( ( b = in . read ( ) ) ! = '\r' & & b ! = - 1 ) {
buf . write ( b ) ;
}
if ( b = = - 1 ) return null ;
b = in . read ( ) ; // read \n
if ( b = = - 1 ) return null ;
return buf . toString ( " UTF-8 " ) ;
}
2011-06-21 00:53:43 +02:00
2009-07-19 22:37:44 +02:00
}