All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.nrc.cadc.net.HttpDownload Maven / Gradle / Ivy

The newest version!
/*
************************************************************************
*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
*
*  (c) 2020.                            (c) 2020.
*  Government of Canada                 Gouvernement du Canada
*  National Research Council            Conseil national de recherches
*  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
*  All rights reserved                  Tous droits réservés
*                                       
*  NRC disclaims any warranties,        Le CNRC dénie toute garantie
*  expressed, implied, or               énoncée, implicite ou légale,
*  statutory, of any kind with          de quelque nature que ce
*  respect to the software,             soit, concernant le logiciel,
*  including without limitation         y compris sans restriction
*  any warranty of merchantability      toute garantie de valeur
*  or fitness for a particular          marchande ou de pertinence
*  purpose. NRC shall not be            pour un usage particulier.
*  liable in any event for any          Le CNRC ne pourra en aucun cas
*  damages, whether direct or           être tenu responsable de tout
*  indirect, special or general,        dommage, direct ou indirect,
*  consequential or incidental,         particulier ou général,
*  arising from the use of the          accessoire ou fortuit, résultant
*  software.  Neither the name          de l'utilisation du logiciel. Ni
*  of the National Research             le nom du Conseil National de
*  Council of Canada nor the            Recherches du Canada ni les noms
*  names of its contributors may        de ses  participants ne peuvent
*  be used to endorse or promote        être utilisés pour approuver ou
*  products derived from this           promouvoir les produits dérivés
*  software without specific prior      de ce logiciel sans autorisation
*  written permission.                  préalable et particulière
*                                       par écrit.
*                                       
*  This file is part of the             Ce fichier fait partie du projet
*  OpenCADC project.                    OpenCADC.
*                                       
*  OpenCADC is free software:           OpenCADC est un logiciel libre ;
*  you can redistribute it and/or       vous pouvez le redistribuer ou le
*  modify it under the terms of         modifier suivant les termes de
*  the GNU Affero General Public        la “GNU Affero General Public
*  License as published by the          License” telle que publiée
*  Free Software Foundation,            par la Free Software Foundation
*  either version 3 of the              : soit la version 3 de cette
*  License, or (at your option)         licence, soit (à votre gré)
*  any later version.                   toute version ultérieure.
*                                       
*  OpenCADC is distributed in the       OpenCADC est distribué
*  hope that it will be useful,         dans l’espoir qu’il vous
*  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
*  without even the implied             GARANTIE : sans même la garantie
*  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
*  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
*  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
*  General Public License for           Générale Publique GNU Affero
*  more details.                        pour plus de détails.
*                                       
*  You should have received             Vous devriez avoir reçu une
*  a copy of the GNU Affero             copie de la Licence Générale
*  General Public License along         Publique GNU Affero avec
*  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
*  .      pas le cas, consultez :
*                                       .
*
*  $Revision: 5 $
*
************************************************************************
 */

package ca.nrc.cadc.net;

import ca.nrc.cadc.auth.NotAuthenticatedException;
import ca.nrc.cadc.io.ByteLimitExceededException;
import ca.nrc.cadc.net.event.TransferEvent;
import ca.nrc.cadc.util.FileMetadata;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipInputStream;
import javax.net.ssl.HttpsURLConnection;
import org.apache.log4j.Logger;

/**
 * Simple task to encapsulate a single download (GET). This class supports http and https
 * (SSL) with optional client certificate authentication using a SSLSocketFactory created one from
 * the current Subject. This class also supports retrying downloads if the server responds
 * with a 503 and a valid Retry-After header, where valid means an integer (number of seconds)
 * that is between 0 and HttpTransfer.MAX_RETRY_DELAY.
 * 
 * 

Note: Redirects are followed by default. * * @author pdowler */ public class HttpDownload extends HttpTransfer { private static Logger log = Logger.getLogger(HttpDownload.class); private static int NONE = 0; private static int GZIP = 1; private static int ZIP = 2; private boolean headOnly = false; private String logAction = "HTTP GET"; private boolean decompress = false; private boolean overwrite = false; private File destDir = null; private File origFile; private File decompFile; private File removeFile; private int decompressor; private OutputStream destStream; private String serverFilename; private File destFile; private InputStreamWrapper wrapper; //private boolean skipped = false; private long decompSize = -1; private long size = -1; private OverwriteChooser overwriteChooser; /** * Constructor with default user-agent string. * * @see HttpDownload (String, URL, File) * @param src URL to read * @param dest file or directory to write to */ public HttpDownload(URL src, File dest) { this(null, src, dest); } /** * @see HttpDownload (String, URL, OutputStream) * @param src URL to read * @param dest output stream to write to * @deprecated use HttpGet */ @Deprecated public HttpDownload(URL src, OutputStream dest) { this(null, src, dest); } /** * @param src * @param dest * @deprecated use HttpGet */ @Deprecated public HttpDownload(URL src, InputStreamWrapper dest) { this(null, src, dest); } /** * Constructor. If the user agent string is not supplied, a default value will be generated. *

* The src URL cannot be null. If the protocol is https, this class will get the current Subject from * the AccessControlContext and use the Certificate(s) and PrivateKey(s) found there to set up an * SSLSocketFactory. *

*

* The dest File cannot be null. If dest is a directory, the downloaded * file will be saved in that directory and the filename will be determined from the HTTP headers or * URL. If dest is an existing file or it does not exist but it's parent is a directory, dest will * be used directly. *

* * @param userAgent user-agent string to report in HTTP headers * @param url URL to read * @param dest file or directory to write to */ public HttpDownload(String userAgent, URL url, File dest) { super(url, true); setUserAgent(userAgent); if (url == null) { throw new IllegalArgumentException("source URL cannot be null"); } if (dest == null) { throw new IllegalArgumentException("destination File cannot be null"); } if (dest.exists() && dest.isDirectory()) { this.destDir = dest; } else { File parent = dest.getParentFile(); if (parent == null) { // relative path throw new IllegalArgumentException("destination File cannot be relative"); } //if (parent.exists() && parent.isDirectory()) //{ this.destDir = parent; this.localFile = dest; //} //else // throw new IllegalArgumentException("destination File parent must be a directory that exists"); } // dest does not exist == dest is the file to write // dest exists and is a file = dest is the file to (over)write // dest exists and it a directory == dest is the parent, we determine filename // all other path components are directories, if the do not exist we create them } /** * Constructor. If the user agent string is not supplied, a default value will be generated. * *

* The src URL cannot be null. If the protocol is https, this class will get the current Subject from * the AccessControlContext and use the Certificate(s) and PrivateKey(s) found there to set up an * SSLSocketFactory. *

* *

* The dest output stream cannot be null. *

* * @param userAgent user-agent string to report in HTTP headers * @param url URL to read * @param dest output stream to write to * @deprecated use HttpGet */ @Deprecated public HttpDownload(String userAgent, URL url, OutputStream dest) { super(url, true); setUserAgent(userAgent); if (dest == null) { throw new IllegalArgumentException("destination stream cannot be null"); } this.destStream = dest; } /** * * @param userAgent * @param url * @param dest * @deprecated use HttpGet */ @Deprecated public HttpDownload(String userAgent, URL url, InputStreamWrapper dest) { super(url, true); setUserAgent(userAgent); if (dest == null) { throw new IllegalArgumentException("destination wrapper cannot be null"); } this.wrapper = dest; } @Override public String toString() { return "HttpDownload[" + remoteURL + "," + localFile + "]"; } /** * Set mode so only an HTTP HEAD will be performed. After the download is run(), * the http header parameters from the response can be checked via various * get methods. * * @param headOnly */ public void setHeadOnly(boolean headOnly) { this.headOnly = headOnly; if (headOnly) { this.logAction = "HTTP HEAD"; } } /** * Enable optional decompression of the data after download. GZIP and ZIP are supported. * * @param decompress */ public void setDecompress(boolean decompress) { this.decompress = decompress; } public void setOverwriteChooser(OverwriteChooser overwriteChooser) { this.overwriteChooser = overwriteChooser; } /** * Enable forced overwrite of existing destination file. * * @param overwrite */ public void setOverwrite(boolean overwrite) { this.overwrite = overwrite; } /** * Get the size of the result file. This may be smaller than the content-length if the * file is being decompressed. * * @return the size in bytes, or -1 of unknown */ public long getSize() { return size; } public String getFilename() { return serverFilename; } /** * Get a reference to the result file. In some cases this is null until the * download is complete. * * @return reference to the output file or null if download failed */ public File getFile() { return destFile; } @Override public void prepare() throws AccessControlException, NotAuthenticatedException, ByteLimitExceededException, ExpectationFailedException, IllegalArgumentException, PreconditionFailedException, ResourceAlreadyExistsException, ResourceNotFoundException, TransientException, IOException, InterruptedException { // not feasible to separate all the setup and defer the read // to run() throw new UnsupportedOperationException("TODO"); } @Override public void run() { try { doActionWithRetryLoop(); } catch (Throwable t) { this.failure = t; } finally { responseStream = null; } } @Override protected void doAction() throws AccessControlException, NotAuthenticatedException, ByteLimitExceededException, ExpectationFailedException, IllegalArgumentException, PreconditionFailedException, ResourceAlreadyExistsException, ResourceNotFoundException, TransientException, IOException, InterruptedException, RangeNotSatisfiableException { log.debug(this.toString()); if (!go) { return; // cancelled while queued, event notification handled in terminate() } boolean throwTE = false; URL currentURL = remoteURL; try { // store the thread so that other threads (typically the // Swing event thread) can terminate the Download this.thread = Thread.currentThread(); fireEvent(TransferEvent.CONNECTING); boolean done = false; List visitedURLs = new ArrayList(); while (!done) { done = true; doGet(currentURL); if (followRedirects && redirectURL != null) { if (visitedURLs.contains(redirectURL)) { throw new IllegalArgumentException("redirect back to a previously visited URL: " + redirectURL); } if (visitedURLs.size() > 6) { throw new IllegalArgumentException("redirect exceeded hard-coded limit (6): " + redirectURL); } visitedURLs.add(currentURL); currentURL = redirectURL; redirectURL = null; done = false; } } if (decompress && decompressor != NONE) { fireEvent(decompFile, TransferEvent.DECOMPRESSING); doDecompress(); } } finally { synchronized (this) { // vs sync block in terminate() if (thread != null) { // clear interrupt status if (Thread.interrupted()) { go = false; } this.thread = null; } } if (failure == null && removeFile != null) { // only remove if download was successful log.debug("removing: " + removeFile); fireEvent(removeFile, TransferEvent.DELETED); removeFile.delete(); } if (!go) { log.debug("cancelled"); fireEvent(TransferEvent.CANCELLED); } else if (failure != null) { log.debug("failed: " + failure); fireEvent(failure); } else if (!throwTE) { log.debug("completed"); FileMetadata meta = new FileMetadata(); meta.setContentType(getContentType()); meta.setContentEncoding(getContentEncoding()); meta.setContentLength(getContentLength()); meta.setMd5Sum(getContentMD5()); meta.setLastModified(getLastModified()); fireEvent(destFile, TransferEvent.COMPLETED, meta); } } } protected boolean askOverwrite(File f, Long length, Date lastModified) { // chooser API mismatch Long lastMod = null; if (lastModified != null) { lastMod = lastModified.getTime(); } return overwrite || (overwriteChooser != null && overwriteChooser.overwriteFile(f.getAbsolutePath(), f.length(), f.lastModified(), length, lastMod)); } // determine which file to read and write, enable optional decompression private boolean doCheckDestination() throws InterruptedException { // check/clear interrupted flag and throw if necessary if (Thread.interrupted()) { throw new InterruptedException(); } boolean doDownload = true; if (origFile.exists()) { log.debug(origFile + " exists"); if (askOverwrite(origFile, getContentLength(), getLastModified())) { log.debug("overwrite: YES -- " + origFile); origFile.delete(); if (decompFile != null && decompFile.exists()) { decompFile.delete(); } if (decompress && decompressor != NONE) { this.destFile = decompFile; // download and decompress } else { this.destFile = origFile; // download } } else { log.debug("overwrite: NO -- " + origFile); if (decompress && decompressor != NONE) { decompFile.delete(); doDownload = false; this.destFile = decompFile; // decomp only this.removeFile = origFile; // remove after decompress } else { doDownload = false; //this.skipped = true; } } } else if (decompFile != null && decompFile.exists()) { log.debug(decompFile + " exists"); if (askOverwrite(decompFile, decompSize, getLastModified())) { log.debug("overwrite: YES -- " + decompFile); //decompFile.delete(); // origFile does not exist this.removeFile = decompFile; if (decompress && decompressor != NONE) { this.destFile = decompFile; // download and decompress } else { this.destFile = origFile; // download } } else { log.debug("overwrite: NO -- " + decompFile); this.destFile = decompFile; this.removeFile = null; doDownload = false; //this.skipped = true; } } else if (decompress && decompressor != NONE && decompFile != null) { this.destFile = decompFile; } else { this.destFile = origFile; } log.debug("destination file: " + destFile); this.localFile = destFile; return doDownload; } // called from doHead and doGet to capture HTTP standard header values private void processHeader(HttpURLConnection conn) throws IOException, InterruptedException { // custom CADC header String ucl = conn.getHeaderField("X-Uncompressed-Length"); if (ucl != null) { try { this.decompSize = Long.parseLong(ucl); } catch (NumberFormatException ignore) { // do nothing } } this.serverFilename = getServerFilename(conn); if (destStream == null && wrapper == null) { // download to file: extra metadata String origFilename = null; // first option: use what the caller suggested if (localFile != destDir) { this.origFile = localFile; } if (origFile == null) { this.origFile = new File(destDir, serverFilename); } origFilename = origFile.getName(); // encoding mucks with filename if ("gzip".equals(getContentEncoding()) || origFilename.endsWith(".gz")) { if (origFilename.endsWith(".gz")) { this.decompFile = new File(destDir, origFilename.substring(0, origFilename.length() - 3)); } else { this.decompFile = origFile; this.origFile = new File(destDir, origFilename + ".gz"); } this.decompressor = GZIP; } else if ("zip".equals(getContentEncoding()) || origFilename.endsWith(".zip")) { if (origFilename.endsWith(".zip")) { this.decompFile = new File(destDir, origFilename.substring(0, origFilename.length() - 4)); } else { this.decompFile = origFile; this.origFile = new File(destDir, origFilename + ".zip"); } this.decompressor = ZIP; } } log.debug(" original file: " + origFile); log.debug(" decomp file: " + decompFile); log.debug(" content length: " + getContentLength()); log.debug(" content md5: " + getContentMD5()); log.debug(" content type: " + getContentType()); log.debug("content encoding: " + getContentEncoding()); log.debug(" decomp size: " + decompSize); log.debug(" decompressor: " + decompressor); log.debug(" lastModified: " + getLastModified()); } private String getServerFilename(HttpURLConnection conn) { String ret = null; // first option: use supplied filename if present in http header String cdisp = conn.getHeaderField("Content-Disposition"); log.debug("HTTP HEAD: Content-Disposition = " + cdisp); if (cdisp != null) { ret = parseContentDisposition(cdisp); } // alternative: pull something from the end of the URL if (ret == null) { String s = remoteURL.getPath(); String query = remoteURL.getQuery(); int i = s.lastIndexOf('/'); if (i != -1 && i < s.length() - 1) { ret = s.substring(i + 1, s.length()); } if (query != null) { ret += "?" + query; } } // last resort for no path: use hostname if (ret == null) { ret = remoteURL.getHost(); } return ret; } private void doGet(URL url) throws AccessControlException, NotAuthenticatedException, ByteLimitExceededException, ExpectationFailedException, IllegalArgumentException, PreconditionFailedException, ResourceAlreadyExistsException, ResourceNotFoundException, TransientException, IOException, InterruptedException, RangeNotSatisfiableException { // check/clear interrupted flag and throw if necessary if (Thread.interrupted()) { throw new InterruptedException(); } InputStream istream = null; OutputStream ostream = null; try { // open connection HttpURLConnection conn = (HttpURLConnection) url.openConnection(); super.setRequestOptions(conn); log.debug("HttpURLConnection type: " + conn.getClass().getName() + " for GET " + url); if (headOnly) { conn.setRequestMethod("HEAD"); } else { conn.setRequestMethod("GET"); } setRequestAuthHeaders(conn); if (conn instanceof HttpsURLConnection) { HttpsURLConnection sslConn = (HttpsURLConnection) conn; initHTTPS(sslConn); } conn.setInstanceFollowRedirects(followRedirects); conn.setRequestProperty("Accept", "*/*"); setRequestHeaders(conn); requestStartTime = System.currentTimeMillis(); checkErrors(url, conn); checkRedirects(url, conn); processHeader(conn); if (headOnly || (!followRedirects && redirectURL != null)) { return; } // evaulate overwrite of complete file boolean doDownload = true; if (destStream == null && wrapper == null) { doDownload = doCheckDestination(); } // go=false means cancelled, doDownload==false means skipped go = go && doDownload; if (!go) { return; } // evaluate possible resume? File tmp = origFile; this.size = getContentLength(); String pkey = null; String pvalue = null; boolean append = false; long startingPos = 0; if (destStream == null && wrapper == null) { // downloading to file // temporary destination origFile = new File(origFile.getAbsolutePath() + ".part"); if (origFile.exists() && origFile.length() < getContentLength()) { // partial file from previous download pkey = "Range"; pvalue = "bytes=" + origFile.length() + "-"; // open ended } } if (pkey != null) { // open 2nd connection with a range request HttpURLConnection rconn = (HttpURLConnection) url.openConnection(); super.setRequestOptions(rconn); log.debug("HttpURLConnection type: " + rconn.getClass().getName() + " for GET " + url); rconn.setRequestMethod("GET"); setRequestAuthHeaders(rconn); if (rconn instanceof HttpsURLConnection) { HttpsURLConnection sslConn = (HttpsURLConnection) rconn; initHTTPS(sslConn); } rconn.setInstanceFollowRedirects(true); rconn.setRequestProperty("Accept", "*/*"); setRequestHeaders(rconn); log.debug("trying: " + pkey + " = " + pvalue); rconn.setRequestProperty(pkey, pvalue); int rcode = rconn.getResponseCode(); log.debug(logAction + " status: " + rcode + " for range request to " + url); if (rcode == HttpURLConnection.HTTP_PARTIAL) { String cr = conn.getHeaderField("Content-Range"); log.debug("Content-Range = " + cr); if (cr != null) { cr = cr.trim(); if (cr.startsWith("bytes")) { cr = cr.substring(6); String[] parts = cr.split("-"); startingPos = Long.parseLong(parts[0]); log.debug("found startingPos = " + startingPos); String[] ss = cr.split("/"); this.size = Long.parseLong(ss[1]); log.debug("found real size = " + size); append = true; } } if (append) { try { log.debug("can resume: closing first connection"); conn.disconnect(); } catch (Exception ignore) { // do nothing } conn = rconn; // use the second connection with partial checkErrors(url, conn); // recapture responseCode, headers, etc } else { // proceed with original connection try { log.debug("cannot resume: closing second connection"); rconn.disconnect(); } catch (Exception ignore) { // do nothing } } } } fireEvent(TransferEvent.CONNECTED); // check eventID hook findEventID(conn); fireEvent(origFile, TransferEvent.TRANSFERING); istream = conn.getInputStream(); if (!(istream instanceof BufferedInputStream)) { log.debug("using BufferedInputStream"); istream = new BufferedInputStream(istream, bufferSize); } if (this.destStream != null) { log.debug("output: supplied OutputStream"); ostream = destStream; log.debug("using BufferedOutputStream"); ostream = new BufferedOutputStream(ostream, bufferSize); } else if (wrapper == null) { // prepare to write to origFile File parent = origFile.getParentFile(); parent.mkdirs(); if (!parent.exists()) { throw new IOException("failed to create one or more parent dir(s):" + parent); } log.debug("output: " + origFile + " append: " + append); ostream = new FileOutputStream(origFile, append); log.debug("using BufferedOutputStream"); ostream = new BufferedOutputStream(ostream, bufferSize); } // TODO: use super.readResponse if (wrapper != null) { wrapper.read(istream); } else if (userNio) { nioLoop(istream, ostream, 2 * bufferSize, startingPos); } else { String md5 = ioLoop(istream, ostream, 2 * bufferSize, startingPos); if (getContentMD5() != null && md5 != null) { if (!md5.equals(getContentMD5())) { StringBuilder sb = new StringBuilder(); sb.append("MD5 mismatch: "); sb.append(getContentMD5()).append(" (header) != ").append(md5).append(" (bytes)"); if (url != null) { sb.append(" url: ").append(url); } if (destFile != null) { sb.append(" destFile: ").append(destFile.getAbsolutePath()); } throw new IncorrectContentChecksumException(sb.toString()); } } } if (ostream != null) { long writeStart = System.currentTimeMillis(); ostream.flush(); if (logIO) { long flushTime = System.currentTimeMillis() - writeStart; writeTime += flushTime; log.debug("Time (ms) to flush: " + flushTime); } } log.debug("download completed"); if (destStream == null && wrapper == null) { // downloading to file log.debug("renaming " + origFile + " to " + tmp); origFile.renameTo(tmp); origFile = tmp; destFile = tmp; } } finally { if (istream != null) { log.debug("closing InputStream"); try { istream.close(); } catch (Exception ignore) { // do nothing } } if (ostream != null) { log.debug("closing OutputStream"); try { ostream.close(); } catch (Exception ignore) { // do nothing } } } } private void doDecompress() throws IOException, InterruptedException { // check/clear interrupted flag and throw if necessary if (Thread.interrupted()) { throw new InterruptedException(); } InputStream istream = null; OutputStream ostream = null; //RandomAccessFile ostream = null; try { this.size = decompSize; int sz = bufferSize; if (decompressor == GZIP) { log.debug("input: GZIPInputStream(BufferedInputStream(FileInputStream)"); istream = new GZIPInputStream(new FileInputStream(origFile), sz); } else if (decompressor == ZIP) { log.debug("input: ZIPInputStream(BufferedInputStream(FileInputStream)"); istream = new ZipInputStream(new BufferedInputStream(new FileInputStream(origFile))); } log.debug("output: " + decompFile); ostream = new BufferedOutputStream(new FileOutputStream(decompFile), sz); this.removeFile = origFile; if (userNio) { nioLoop(istream, ostream, sz, 0); } else { ioLoop(istream, ostream, sz, 0); } ostream.flush(); this.destFile = decompFile; // ?? } finally { if (istream != null) { try { istream.close(); } catch (Exception ignore) { // do nothing } } if (ostream != null) { try { ostream.close(); } catch (Exception ignore) { // do nothing } } } } private static char SINGLE_QUOTE = "'".charAt(0); private static char DOUBLE_QUOTE = "\"".charAt(0); private static boolean isFilenameDisposition(String cdisp) { if (cdisp == null) { return false; } cdisp = cdisp.toLowerCase(); // just for checking // HACK: HTTP/1.1 allows attachment or extension token, but some sites use inline anyway return (cdisp.startsWith("attachment") || cdisp.startsWith("inline")); } /** * Parse a Content-Disposition header value and extract the filename. * * @param cdisp value of the Content-Disposition header * @return a filename, or null */ public static String parseContentDisposition(String cdisp) { if (!isFilenameDisposition(cdisp)) { return null; } // TODO: should split on ; and check each part for filename=something // extra filename from cdisp value String[] parts = cdisp.split(";"); for (int p = 0; p < parts.length; p++) { String part = parts[p].trim(); // check/remove double quotes if (part.charAt(0) == '"') { part = part.substring(1, part.length()); } if (part.charAt(part.length() - 1) == '"') { part = part.substring(0, part.length() - 1); } if (part.startsWith("filename")) { int i = part.indexOf('='); String filename = part.substring(i + 1, part.length()); // strip off optional quotes char c1 = filename.charAt(0); char c2 = filename.charAt(filename.length() - 1); boolean rs = (c1 == SINGLE_QUOTE || c1 == DOUBLE_QUOTE); boolean re = (c2 == SINGLE_QUOTE || c2 == DOUBLE_QUOTE); if (rs && re) { filename = filename.substring(1, filename.length() - 1); } else if (rs) { filename = filename.substring(1, filename.length()); } else if (re) { filename = filename.substring(0, filename.length() - 1); } // strip off optional path information i = filename.lastIndexOf('/'); // unix if (i >= 0) { filename = filename.substring(i + 1); } i = filename.lastIndexOf('\\'); // windows if (i >= 0) { filename = filename.substring(i + 1); } // TODO: check/sanitize for security issues return filename; } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy