You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by st...@apache.org on 2006/06/27 15:26:42 UTC

svn commit: r417451 [2/2] - in /ant/sandbox/antlibs/http/trunk: ./ docs/ src/ src/etc/ src/etc/testcases/ src/etc/testcases/http/ src/main/ src/main/org/ src/main/org/apache/ src/main/org/apache/ant/ src/main/org/apache/ant/http/ src/war/ src/war/WEB-I...

Added: ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/HttpTask.java
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/HttpTask.java?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/HttpTask.java (added)
+++ ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/HttpTask.java Tue Jun 27 06:26:40 2006
@@ -0,0 +1,1083 @@
+/*
+ * Copyright  2001-2006 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+
+package org.apache.ant.http;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Date;
+import java.util.Vector;
+
+/**
+ * This class is a foundational class for all the tasks which implement http
+ * methods. To implement a subclass you *must* provide an implementation of
+ * getRequestMethod(). Consider also stating the parameter policy
+ * (areParamsAddedToUrl()) and then, if needed, overriding doConnect, and the
+ * onConnected(), OnDownloadFinished() methods.
+ *
+ * @created March 17, 2001
+ */
+public abstract class HttpTask extends Task {
+
+    /**
+     * flag to control action on execution trouble.
+     */
+    protected boolean failOnError = true;
+
+    /**
+     * this sets the size of the buffer and the hash for download (Kilobytes).
+     */
+
+    protected int blockSize = 64;
+
+    /**
+     * property to set on success.
+     */
+
+    protected String status;
+
+    /**
+     * source URL- required.
+     */
+    private String source;
+
+    /**
+     * destination for download.
+     */
+    private File destFile;
+    /**
+     * verbose flag gives extra information.
+     */
+    private boolean verbose = false;
+
+    /**
+     * timestamp based download flag. off by default.
+     */
+    private boolean useTimestamp = false;
+
+    /**
+     * authorization mechanism in use.
+     */
+    private int authType = AUTH_NONE;
+
+    /**
+     * username for authentication.
+     */
+    private String username;
+
+    /**
+     * password for authentication.
+     */
+    private String password;
+
+    /**
+     * parameters to send on a request.
+     */
+    private Vector params = new Vector();
+
+    /**
+     * headers to send on a request
+     */
+    private Vector headers = new Vector();
+
+    /**
+     * cache policy.
+     */
+    private boolean usecaches = false;
+
+    /**
+     * the name of a destination property.
+     */
+
+    private String destProperty = null;
+
+    /**
+     * a flag to control whether or not response codes are acted on.
+     */
+    private boolean useResponseCode = true;
+
+    /**
+     * No authentication specified.
+     */
+    public final static int AUTH_NONE = 0;
+
+    /**
+     * basic 'cleartext' authentication.
+     */
+    public final static int AUTH_BASIC = 1;
+
+    /**
+     * digest auth. not actually supported but present for completeness.
+     */
+    public final static int AUTH_DIGEST = 2;
+
+
+    /**
+     * turn caching on or off. only relevant for protocols and methods which are
+     * cacheable (HEAD, GET) on http.
+     *
+     * @param usecaches The new UseCaches value
+     */
+    public void setUseCaches(boolean usecaches) {
+        this.usecaches = usecaches;
+    }
+
+    /**
+     * control whether response codes are used to determine success/failure.
+     *
+     * @param useResponseCode the new value
+     */
+    public void setUseResponseCode(boolean useResponseCode) {
+        this.useResponseCode = useResponseCode;
+    }
+
+
+    /**
+     * Set the URL.
+     *
+     * @param u URL for the operation.
+     */
+    public void setURL(String u) {
+        this.source = u;
+    }
+
+
+    /**
+     * the local destination for any response. this can be null for "don't
+     * download".
+     *
+     * @param destFile Path to file.
+     */
+    public void setDestFile(File destFile) {
+        this.destFile = destFile;
+    }
+
+    /**
+     * the local destination for any response. this can be null for "don't
+     * download"
+     *
+     * @param name Path to file.
+     */
+    public void setDestinationProperty(String name) {
+        this.destProperty = name;
+    }
+
+
+    /**
+     * Be verbose, if set to " <CODE>true</CODE> ".
+     *
+     * @param verbose The new Verbose value
+     */
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+
+    /**
+     * set the fail on error flag.
+     *
+     * @param b The new FailOnError value
+     */
+    public void setFailOnError(boolean b) {
+        failOnError = b;
+    }
+
+
+    /**
+     * Use timestamps, if set to " <CODE>true</CODE> ". <p>
+     * <p/>
+     * In this situation, the if-modified-since header is set so that the file
+     * is only fetched if it is newer than the local file (or there is no local
+     * file) This flag is only valid on HTTP connections, it is ignored in other
+     * cases. When the flag is set, the local copy of the downloaded file will
+     * also have its timestamp set to the remote file time. <br> Note that
+     * remote files of date 1/1/1970 (GMT) are treated as 'no timestamp', and
+     * web servers often serve files with a timestamp in the future by replacing
+     * their timestamp with that of the current time. Also, inter-computer clock
+     * differences can cause no end of grief.
+     *
+     * @param usetimestamp The new UseTimestamp value
+     */
+    public void setUseTimestamp(boolean usetimestamp) {
+        this.useTimestamp = usetimestamp;
+    }
+
+
+    /**
+     * Sets the Authtype attribute of the HttpTask object REVISIT/REFACTOR.
+     *
+     * @param type The new Authtype value
+     */
+    public void setAuthtype(AuthMethodType type) {
+        this.authType = type.getIndex();
+    }
+
+
+    /**
+     * Sets the Username used for authentication. setting the username
+     * implicitly turns authentication on.
+     *
+     * @param username The new Username value
+     */
+    public void setUsername(String username) {
+        this.username = username;
+        if (authType == AUTH_NONE) {
+            authType = AUTH_BASIC;
+        }
+    }
+
+
+    /**
+     * Sets the Password for an authenticated request.
+     *
+     * @param password The new Password value
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+
+    /**
+     * set a property to be set in the event of success.
+     *
+     * @param status The new SuccessProperty value
+     */
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    /**
+     * get the block size in kilobytes.
+     */
+
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * set the new block size for download (in kilobytes).
+     *
+     * @param blocksize the new value 
+     */
+    public void setBlockSize(int blocksize) {
+        this.blockSize = blocksize;
+    }
+
+
+    /**
+     * query cache policy.
+     *
+     * @return The UseCaches value
+     */
+    public boolean getUseCaches() {
+        return usecaches;
+    }
+
+
+    /**
+     * query fail on error flag.
+     *
+     * @return The FailFailOnError value
+     */
+    public boolean getFailOnError() {
+        return failOnError;
+    }
+
+
+    /**
+     * get the username.
+     *
+     * @return current username or null for 'none'
+     */
+    public String getUsername() {
+        return username;
+    }
+
+
+    /**
+     * get the password.
+     *
+     * @return current password or null for 'none'
+     */
+    public String getPassword() {
+        return password;
+    }
+
+
+    /**
+     * @return The RemoteURL value.
+     */
+    public String getURL() {
+        return source;
+    }
+
+
+    /**
+     * Get the vector of access parameters.
+     *
+     * @return The RequestParameters value
+     */
+    public Vector getRequestParameters() {
+        return params;
+    }
+
+
+    /**
+     * accessor of success property name
+     *
+     * @return The SuccessProperty value
+     */
+    public String getStatus() {
+        return status;
+    }
+
+    /**
+     * accessor of destination property name
+     *
+     * @return The destination value
+     */
+    public String getDestinationProperty() {
+        return destProperty;
+    }
+
+    /**
+     * accessor of destination
+     *
+     * @return Thedestination
+     */
+    public File getDestFile() {
+        return destFile;
+    }
+
+
+    /**
+     * if the user wanted a success property, this sets it. of course, it is only
+     * relevant if failonerror=false
+     */
+
+    public void noteSuccess() {
+        if (status != null && status.length() > 0) {
+            getProject().setProperty(status, "true");
+        }
+    }
+
+
+    /**
+     * Does the work.
+     *
+     * @throws BuildException Thrown in unrecoverable error.
+     */
+    public void execute() {
+
+        //check arguments, will bail out if there
+        //was trouble 
+        verifyArguments();
+
+        //set up the URL connection
+        URL url = buildURL();
+
+        try {
+
+            //now create a connection
+            URLConnection connection = url.openConnection();
+
+            //set caching option to whatever
+            connection.setUseCaches(getUseCaches());
+
+            //set the timestamp option if flag is set and
+            //the local file actually exists.
+            long localTimestamp = getTimestamp();
+            if (localTimestamp != 0) {
+                if (verbose) {
+                    Date t = new Date(localTimestamp);
+                    log("local file date : " + t.toString());
+                }
+                connection.setIfModifiedSince(localTimestamp);
+            }
+
+            // Set auth header, if specified
+            //NB: verifyArguments will already have checked that you can't
+            //have a null username with a non-null strategy.
+            HttpAuthenticationStrategy authStrategy = getAuthStrategy();
+            if (authStrategy != null) {
+                authStrategy.setAuthenticationHeader(connection,
+                        null,
+                        username,
+                        password);
+            }
+
+            // Set explicitly specified request headers
+            HttpRequestParameter header;
+            for (int i = 0; i < headers.size(); i++) {
+                header = (HttpRequestParameter) headers.get(i);
+                connection.setRequestProperty(header.getName(),
+                        header.getValue());
+            }
+
+            //cast to an http connection if we can,
+            //then set the request method pulled from the subclass
+            String method = getRequestMethod();
+            HttpURLConnection httpConnection = null;
+            if (connection instanceof HttpURLConnection) {
+                httpConnection = (HttpURLConnection) connection;
+                httpConnection.setRequestMethod(method);
+            }
+            log("making " + method + " to " + url);
+
+            //call self or subclass for the connect.
+            //the connection object may change identity at this point.
+            connection = doConnect(connection);
+
+            //then provide a bit of overridable post processing for the fun of it
+            if (!onConnected(connection)) {
+                return;
+            }
+
+            //repeat the cast.
+            if (connection instanceof HttpURLConnection) {
+                httpConnection = (HttpURLConnection) connection;
+            }
+            if (httpConnection != null) {
+                // check for a 304 result (HTTP only) when we set the timestamp
+                // earlier on (A fractional performance tweak)
+                if (localTimestamp != 0) {
+                    if (getResponseCode(httpConnection) ==
+                            HttpURLConnection
+                                    .HTTP_NOT_MODIFIED) {
+                        //not modified so no file download. just return instead
+                        //and trace out something so the user doesn't think that the
+                        //download happened when it didn't
+                        log("Local file is up to date - so nothing was downloaded");
+                        noteSuccess();
+                        return;
+                    }
+                }
+
+            }
+
+            //get the input stream
+            InputStream is = getInputStream(connection);
+
+            //bail out if the input stream isn't valid at this point
+            //again, though we should have got to this point earlier.
+
+            if (is == null) {
+                log("Can't get " + url, Project.MSG_ERR);
+                if (getFailOnError()) {
+                    return;
+                }
+                throw new BuildException("Can't reach URL");
+            }
+
+            //pick a file or null stream for saving content
+            OutputStream out = null;
+            if (destFile != null) {
+                log("Saving output to " + destFile, Project.MSG_DEBUG);
+                out = new FileOutputStream(destFile);
+            } else {
+                if (destProperty != null) {
+                    //save contents to a property
+                    log("Saving output to property " + destProperty,
+                            Project.MSG_DEBUG);
+                    out = new ByteArrayOutputStream(blockSize * 1024);
+                } else {
+                    //discard everything
+                    out = new NullOutputStream();
+                }
+            }
+
+            //get content length
+            //do it this way instead of calling getContentLength() because
+            //that way is sporadically unreliable (length is downgraded to 
+            //size of small packets)
+            int contentLength = connection.getHeaderFieldInt("Content-Length",
+                    -1);
+            int bytesRead = 0;
+
+            //now start download.
+            byte[] buffer = new byte[blockSize * 1024];
+            int length;
+
+            while ((length = is.read(buffer)) >= 0 &&
+                    (contentLength == -1 || bytesRead < contentLength)) {
+                bytesRead += length;
+                out.write(buffer, 0, length);
+                if (verbose) {
+                    showProgressChar('.');
+                }
+            }
+
+            //finished successfully - clean up.
+            if (verbose) {
+                showProgressChar('\n');
+            }
+
+            //if it we were saving to a byte array, then
+            //set the destination property with its contents
+            if (out instanceof ByteArrayOutputStream) {
+                getProject().setProperty(destProperty,
+                        out.toString());
+            }
+
+            //everything is downloaded; close files
+            out.flush();
+            out.close();
+            is.close();
+            is = null;
+            out = null;
+
+            //another overridable notification method
+            if (!onDownloadFinished(connection)) {
+                return;
+            }
+
+            //REFACTOR: move this down to HttpHead? What if a post wants
+            //to set a date?
+            //if (and only if) the use file time option is set, then the
+            //saved file now has its timestamp set to that of the downloaded file
+            if (useTimestamp) {
+                long remoteTimestamp = connection.getLastModified();
+                if (verbose) {
+                    Date t = new Date(remoteTimestamp);
+                    log("last modified = " +
+                            t.toString()
+                            +
+                            ((remoteTimestamp ==
+                                    0) ? " - using current time instead" : ""));
+                }
+                if (remoteTimestamp != 0) {
+
+                    destFile.setLastModified(remoteTimestamp);
+                }
+            }
+
+
+            String failureString = null;
+            if (contentLength > -1 && bytesRead != contentLength) {
+                failureString = "Incomplete download -Expected " + contentLength
+                        + "received " + bytesRead + " bytes";
+            } else {
+
+                //finally clean anything up.
+                //http requests have their response code checked, and only
+                //those in the success range are deemed successful.
+                if (httpConnection != null && useResponseCode) {
+                    int statusCode = httpConnection.getResponseCode();
+                    if (statusCode < 200 || statusCode > 299) {
+                        failureString = "Server error code " +
+                                statusCode +
+                                " received";
+                    }
+                }
+            }
+
+            //check for an error message
+            if (failureString == null) {
+                noteSuccess();
+            } else {
+                if (failOnError) {
+                    throw new BuildException(failureString);
+                } else {
+                    log(failureString, Project.MSG_ERR);
+                }
+            }
+
+        }
+        catch (IOException ioe) {
+            log("Error performing " + getRequestMethod() + " on " + url +
+                    " : " + ioe.toString(), Project.MSG_ERR);
+            if (failOnError) {
+                throw new BuildException(ioe);
+            }
+        }
+    }
+
+    /**
+     * show a progress character. Not as useful as you think, given buffering.
+     */
+
+    protected void showProgressChar(char c) {
+        System.out.write(c);
+    }
+
+
+    /**
+     * Adds a form / request parameter.
+     *
+     * @param param The feature to be added to the HttpRequestParameter
+     *              attribute
+     */
+    public void addParam(HttpRequestParameter param) {
+        params.add(param);
+    }
+
+
+    /**
+     * Adds an HTTP request header.
+     *
+     * @param header The feature to be added to the Header attribute
+     */
+    public void addHeader(HttpRequestParameter header) {
+        headers.add(header);
+    }
+
+
+    /**
+     * this must be overridden by implementations to set the request method to
+     * GET, POST, whatever NB: this method only gets called for an http request
+     *
+     * @return the method string
+     */
+    protected abstract String getRequestMethod();
+
+
+    /**
+     * determine the timestamp to use if the flag is set and the local file
+     * actually exists.
+     *
+     * @return 0 for 'no timestamp', a number otherwhise
+     */
+
+    protected long getTimestamp() {
+        long timestamp = 0;
+        if (useTimestamp && destFile != null && destFile.exists()) {
+            timestamp = destFile.lastModified();
+        } else {
+            timestamp = 0;
+        }
+        return timestamp;
+    }
+
+
+    /**
+     * ask for authentication details. An empty string means 'no auth'
+     *
+     * @return an RFC2617 auth string
+     */
+
+    protected String getAuthenticationString() {
+        // Set authorization eader, if specified
+        if (authType == AUTH_BASIC && username != null) {
+            password = password == null ? "" : password;
+            String encodeStr = username + ":" + password;
+            Base64Encode encoder = new Base64Encode();
+            char[] encodedPass = encoder.encodeBase64(encodeStr.getBytes());
+            String authStr = "BASIC " + new String(encodedPass);
+            return authStr;
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * this overridable method verifies that all the params are valid the base
+     * implementation checks for remote url validity and if the destination is
+     * not null, write access to what mustnt be a directory. sublcasses can call
+     * the base class as well as check their own data
+     *
+     * @throws BuildException only throw this when the failonerror flag is true
+     */
+
+    protected void verifyArguments()
+            throws BuildException {
+        //check remote params -but only create an exception, not throw it
+        if (getURL() == null) {
+            throw new BuildException("target URL missing");
+        }
+        //check destination parameters  -but only create an exception, not throw it
+        if (destFile != null && destFile.exists()) {
+            if (destFile.isDirectory()) {
+                throw new BuildException(
+                        "The specified destination is a directory");
+            } else if (!destFile.canWrite()) {
+                throw new BuildException("Can't write to " +
+                        destFile.getAbsolutePath());
+            }
+        }
+        //check auth policy
+        if (authType != AUTH_NONE && username == null) {
+            throw new BuildException(
+                    "no username defined to use with authorisation");
+        }
+    }
+
+
+    /**
+     * build a URL from the source url, maybe with parameters attached
+     *
+     * @return Description of the Returned Value
+     * @throws BuildException Description of Exception
+     */
+    protected URL buildURL()
+            throws BuildException {
+        String urlbase = getURL();
+        try {
+            if (areParamsAddedToUrl()) {
+                urlbase = parameterizeURL();
+            }
+            return new URL(urlbase);
+        }
+        catch (MalformedURLException e) {
+            throw new BuildException("Invalid URL");
+        }
+    }
+
+
+    /**
+     * take a url and add parameters to it. if there are no parameters the base
+     * url string is returned
+     *
+     * @return a string to be used for URL creation
+     * @throws BuildException Description of Exception
+     */
+    protected String parameterizeURL()
+            throws BuildException {
+        //return immediately if there are no parameters
+        if (params.size() == 0) {
+            return getURL();
+        }
+
+        StringBuffer buf = new StringBuffer(getURL());
+        //this devious little line code recognises a parameter string already
+        //in the source url, and if so doesnt add a new one
+        buf.append(source.indexOf('?') == -1 ? '?' : '&');
+        HttpRequestParameter param;
+
+        //run through the parameter list, encode the name/value pairs and
+        //append them to the list
+        for (int i = 0; i < params.size(); i++) {
+            if (i > 0) {
+                buf.append('&');
+            }
+            param = (HttpRequestParameter) params.get(i);
+            buf.append(param.toString());
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * query for the request wanting parameters on the url default is true,
+     * subclasses may want to change
+     *
+     * @return true if a url should have params attached.
+     */
+
+    protected boolean areParamsAddedToUrl() {
+        return true;
+    }
+
+    /**
+     * get the auth policy a null return value means 'no policy chosen'
+     *
+     * @return current authorisation strategy or null
+     */
+
+    protected HttpAuthenticationStrategy getAuthStrategy() {
+        HttpAuthenticationStrategy strategy = null;
+        switch (authType) {
+            case AUTH_BASIC:
+                strategy = new HttpBasicAuth();
+                break;
+            
+            case AUTH_NONE:
+                break;
+
+            case AUTH_DIGEST:
+            default:
+                throw new BuildException("Authentication method "
+                        +authType+" not supported");
+        }
+        return strategy;
+
+    }
+
+    /**
+     * this method opens the connection. It can recognise a 401 error code and
+     * in digest auth will then open a new connection with the supplied nonce
+     * encoded. That is why it can return a new connection object.
+     *
+     * @param connection where to connect to
+     * @return a new connection. This may be different than the old one
+     * @throws BuildException build trouble
+     * @throws IOException    IO trouble
+     */
+
+    protected URLConnection makeConnectionWithAuthHandling(URLConnection connection)
+            throws BuildException, IOException {
+        log("Connecting to " + connection.toString(), Project.MSG_DEBUG);
+        connection.connect();
+        URLConnection returnConnection = connection;
+        log("connected", Project.MSG_DEBUG);
+        if (connection instanceof HttpURLConnection) {
+            HttpURLConnection httpConnection = (HttpURLConnection) connection;
+            if (getResponseCode(httpConnection) ==
+                    HttpURLConnection
+                            .HTTP_UNAUTHORIZED
+                    && authType == AUTH_DIGEST) {
+                //duplicating all the settings then reconnect
+                //and return it
+                log("Digest authentication needed but not yet supported",
+                        Project.MSG_DEBUG);
+            }
+        }
+
+        return returnConnection;
+    }
+
+
+    /**
+     * by making a query for a value from the connection, we force the client
+     * code to actually do the http request and go into input mode. so next we
+     * can check for trouble.
+     */
+    void probeConnection(HttpURLConnection connection) {
+        connection.getHeaderFieldKey(0);
+    }
+
+
+    /**
+     * get a response from a connection request. This code fixes a problem found
+     * in HttpURLConnection, that any attempt to get the response code would
+     * trigger a FileNotFound
+     *
+     * @param connection the current http link
+     * @return whatever we get back
+     * @throws IOException if anything other than file not found gets thrown,
+     *                     and even a FileNotFound exception if that gets thrown
+     *                     too many times.
+     * @see <a href="http://developer.java.sun.com/developer/bugParade/bugs/4160499.html">
+     *      BugParade details </a> "If the requested file does not exist, and
+     *      ends in .html, .htm, .txt or /, you will get the error stream with
+     *      no exception thrown. If the file does not end like any of these you
+     *      can catch the exception and immediately request it again to get the
+     *      error stream. The response code can be obtained with
+     *      getResponseCode()." which means, to really get the response code you
+     *      need to ask twice.
+     */
+    protected int getResponseCode(HttpURLConnection connection)
+            throws IOException {
+        //force the creation of the input stream
+        //(which is what HttpURLConnection.getResponseCode() does internally
+        //that way the bug handler code is only needed once.
+
+        //probeConnection(connection);
+        IOException swallowed = null;
+        boolean caught = false;
+        int response = 0;
+        for (int attempts = 0; attempts < 5; attempts++) {
+            try {
+                response = connection.getResponseCode();
+                caught = true;
+                break;
+            }
+            catch (FileNotFoundException ex) {
+                log("Swallowed FileNotFoundException in getResponseCode",
+                        Project.MSG_VERBOSE);
+                log(ex.toString(), Project.MSG_DEBUG);
+                swallowed = ex;
+            }
+        }
+        if (!caught && swallowed != null) {
+            throw swallowed;
+        }
+        return response;
+    }
+
+    /**
+     * get an input stream from a connection This code tries to fix a problem
+     * found in HttpURLConnection, that any attempt to get the response code
+     * would trigger a FileNotFound BugParade ID 4160499 : <blockquote> "If the
+     * requested file does not exist, and ends in .html, .htm, .txt or /, you
+     * will get the error stream with no exception thrown. If the file does not
+     * end like any of these you can catch the exception and immediately request
+     * it again to get the error stream. The response code can be obtained with
+     * getResponseCode()." <blockquote> which means, to really get the response
+     * code you need to ask twice. More to the point this handling is not
+     * consistent across JVMs: on java 1.3 you can ask as often as you like but
+     * you are not going to get the input stream on a JSP page when it has some
+     * 500 class error.
+     *
+     * @param connection the current link
+     * @return the input stream.
+     * @throws IOException if anything other than file not found gets thrown,
+     *                     and even a FileNotFound exception if that gets thrown
+     *                     too many times.
+     */
+
+    protected InputStream getInputStream(URLConnection connection)
+            throws IOException {
+        IOException swallowed = null;
+        InputStream instream = null;
+        for (int attempts = 0; attempts < 5; attempts++) {
+            try {
+                instream = connection.getInputStream();
+                break;
+            }
+            catch (FileNotFoundException ex) {
+                log("Swallowed IO exception in getInputStream",
+                        Project.MSG_VERBOSE);
+                log(ex.toString(), Project.MSG_DEBUG);
+                swallowed = ex;
+            }
+        }
+        if (instream == null && swallowed != null) {
+            throw swallowed;
+        }
+        return instream;
+    }
+
+
+    /**
+     * this method is inteded for overriding. it is called when connecting to a
+     * URL, and the base implementation just calls connect() on the parameter.
+     * any subclass that wants to pump its own datastream up (like post) must
+     * override this
+     *
+     * @param connection where to connect to
+     * @throws BuildException build trouble
+     * @throws IOException    IO trouble
+     */
+
+    protected URLConnection doConnect(URLConnection connection)
+            throws BuildException, IOException {
+        return makeConnectionWithAuthHandling(connection);
+    }
+
+
+    /**
+     * this is a method for upload centric post-like requests
+     *
+     * @param connection    who we talk to
+     * @param contentType   Description of Parameter
+     * @param contentLength Description of Parameter
+     * @param content       Description of Parameter
+     * @throws IOException something went wrong with the IO
+     */
+    protected URLConnection doConnectWithUpload(URLConnection connection,
+                                                String contentType,
+                                                int contentLength,
+                                                InputStream content)
+            throws IOException {
+
+        log("uploading " + contentLength + " bytes of type " + contentType,
+                Project.MSG_VERBOSE);
+        //tell the connection we are in output mode
+        connection.setDoOutput(true);
+
+        // Set content length and type headers
+        connection.setRequestProperty("Content-Length",
+                String.valueOf(contentLength));
+        connection.setRequestProperty("Content-Type", contentType);
+        connection = makeConnectionWithAuthHandling(connection);
+        OutputStream toServer = connection.getOutputStream();
+
+        //create a buffer which is the smaller of
+        //the content length and the block size (in KB)
+        int buffersize = blockSize * 1024;
+        if (contentLength < buffersize) {
+            buffersize = contentLength;
+        }
+        byte[] buffer = new byte[buffersize];
+        int remaining = contentLength;
+
+        while (remaining > 0) {
+            int read = content.read(buffer);
+            log("block of " + read, Project.MSG_DEBUG);
+            toServer.write(buffer, 0, read);
+            remaining -= read;
+            if (verbose) {
+                showProgressChar('^');
+            }
+        }
+        if (verbose) {
+            showProgressChar('\n');
+        }
+        log("upload completed", Project.MSG_DEBUG);
+        return connection;
+    }
+
+    /**
+     * internal event handler called after a connect can throw an exception or
+     * return false for an immediate exit from the process
+     *
+     * @param connection the now open connection
+     * @return true if the execution is to continue
+     * @throws BuildException Description of Exception
+     */
+    protected boolean onConnected(URLConnection connection)
+            throws BuildException {
+        return true;
+    }
+
+
+    /**
+     * internal event handler called after the download is complete the code can
+     * still bail out at this point, and the connection may contain headers of
+     * interest. can throw an exception or return false for an immediate exit
+     * from the process
+     *
+     * @param connection the now open connection
+     * @return true if the execution is to continue
+     * @throws BuildException Description of Exception
+     */
+    protected boolean onDownloadFinished(URLConnection connection)
+            throws BuildException {
+        return true;
+    }
+
+
+    /**
+     * Enumerated attribute for "authType" with the value "basic" (note,
+     * eventually we can add "digest" authentication)
+     *
+     * @created March 17, 2001
+     */
+    public static class AuthMethodType extends EnumeratedAttribute {
+        /**
+         * Gets the possible values of authorisation supported
+         *
+         * @return The Values value
+         */
+        public String[] getValues() {
+            return new String[]{
+                    "none",
+                    "basic",
+                    //commented out until implemented
+                    //        "digest"
+            };
+        }
+
+    }
+}
+

Added: ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/NullOutputStream.java
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/NullOutputStream.java?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/NullOutputStream.java (added)
+++ ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/NullOutputStream.java Tue Jun 27 06:26:40 2006
@@ -0,0 +1,69 @@
+/*
+ * Copyright  2001-2006 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.ant.http;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * simple output stream which discards all write requests. This should really be
+ * part of java.io, as it is sporadically invaluable
+ *
+ * @created March 17, 2001
+ */
+public final class NullOutputStream extends OutputStream {
+
+    /**
+     * discard all incoming bytes
+     *
+     * @param b byte to write
+     * @throws IOException never throwable in this subclass
+     */
+    public void write(int b)
+            throws IOException {
+    }
+
+
+    /**
+     * discard all incoming bytes
+     *
+     * @param b byte array
+     * @throws IOException never throwable in this subclass
+     */
+    public void write(byte[] b)
+            throws IOException {
+    }
+
+
+    /**
+     * discard all incoming bytes
+     *
+     * @param b   byte array
+     * @param off starting offset
+     * @param len length to write
+     * @throws IOException never throwable in this subclass
+     */
+    public void write(byte[] b,
+                      int off,
+                      int len)
+            throws IOException {
+    }
+
+}
+
+

Added: ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/antlib.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/antlib.xml?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/antlib.xml (added)
+++ ant/sandbox/antlibs/http/trunk/src/main/org/apache/ant/http/antlib.xml Tue Jun 27 06:26:40 2006
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!--
+ Copyright  2006 The Apache Software Foundation
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<antlib xmlns:http="antlib:org.apache.ant.http">
+
+  <taskdef name="get"
+           classname="org.apache.ant.http.HttpGet"/>
+  <taskdef name="post"
+           classname="org.apache.ant.http.HttpPost"/>
+  <taskdef name="head"
+           classname="org.apache.ant.http.HttpHead"/>
+
+</antlib>

Added: ant/sandbox/antlibs/http/trunk/src/war/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/war/WEB-INF/web.xml?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/war/WEB-INF/web.xml (added)
+++ ant/sandbox/antlibs/http/trunk/src/war/WEB-INF/web.xml Tue Jun 27 06:26:40 2006
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
+<web-app>
+</web-app>
\ No newline at end of file

Added: ant/sandbox/antlibs/http/trunk/src/war/resources/error.jsp
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/war/resources/error.jsp?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/war/resources/error.jsp (added)
+++ ant/sandbox/antlibs/http/trunk/src/war/resources/error.jsp Tue Jun 27 06:26:40 2006
@@ -0,0 +1,44 @@
+<%--
+ * Copyright  2001-2006 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+--%>
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%
+    String codeParam = request.getParameter("code");
+
+    int error_code =HttpServletResponse.SC_OK;
+    if(codeParam!=null) {
+        try {
+            error_code =Integer.valueOf(codeParam).intValue();
+        } catch (NumberFormatException e) {
+            error_code =HttpServletResponse.SC_BAD_REQUEST;
+        }
+    } 
+    if(error_code !=HttpServletResponse.SC_OK) {
+        response.sendError(error_code);
+    }
+%>
+<html>
+  <head><title>Error page</title></head>
+  <body>
+  <p>
+  ?code parameter <%= codeParam!=null?codeParam:"absent" %>
+  </p>
+  <p>
+      <%-- this string is searched for in the tests; do not edit without patching 
+      them -->
+  error_code=<%= error_code %>
+  </p>
+  </body>
+</html>
\ No newline at end of file

Added: ant/sandbox/antlibs/http/trunk/src/war/resources/headers.jsp
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/war/resources/headers.jsp?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/war/resources/headers.jsp (added)
+++ ant/sandbox/antlibs/http/trunk/src/war/resources/headers.jsp Tue Jun 27 06:26:40 2006
@@ -0,0 +1,38 @@
+<%@ page import="java.util.Enumeration" %>
+<%--
+    List all headers in the message.
+    If you post a header with HTML or javascript in it wont be escaped, which
+    is a potential XSS security hole. Not for use on public systems 
+--%>
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%!
+    //minimal escaping of angle braces.
+    String escape(String source) {
+        String s1 = source.replace("<", "&lt;");
+        String s2 = source.replace(">", "&gt;");
+        return s2;
+    }
+
+%>
+<html>
+<head><title>Headers</title></head>
+
+<body>
+<h1>Headers</h1>
+<ol>
+<%
+    Enumeration headerNames = request.getHeaderNames();
+    while (headerNames.hasMoreElements()) {
+        String name = (String) headerNames.nextElement();
+        Enumeration values = request.getHeaders(name);
+        while (values.hasMoreElements()) {
+            String value = (String) values.nextElement();
+            out.print(escape(name));
+            out.print('=');
+            out.println(escape(value));
+        }
+    }
+%>
+</ol>
+</body>
+</html>
\ No newline at end of file

Added: ant/sandbox/antlibs/http/trunk/src/war/resources/index.html
URL: http://svn.apache.org/viewvc/ant/sandbox/antlibs/http/trunk/src/war/resources/index.html?rev=417451&view=auto
==============================================================================
--- ant/sandbox/antlibs/http/trunk/src/war/resources/index.html (added)
+++ ant/sandbox/antlibs/http/trunk/src/war/resources/index.html Tue Jun 27 06:26:40 2006
@@ -0,0 +1,15 @@
+<html>
+<head><title>Ant test webapp</title></head>
+<body>
+
+
+<ul>
+    
+    <li><a href="error.jsp">Error page</a></li>
+    <li><a href="headers.jsp">Header listing</a></li>
+</ul>
+
+</body>
+
+
+</html>
\ No newline at end of file



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org