001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider.http;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.HttpURLConnection;
022
023import org.apache.commons.httpclient.Header;
024import org.apache.commons.httpclient.HttpClient;
025import org.apache.commons.httpclient.HttpMethod;
026import org.apache.commons.httpclient.URIException;
027import org.apache.commons.httpclient.methods.GetMethod;
028import org.apache.commons.httpclient.methods.HeadMethod;
029import org.apache.commons.httpclient.util.DateUtil;
030import org.apache.commons.httpclient.util.URIUtil;
031import org.apache.commons.vfs2.FileContentInfoFactory;
032import org.apache.commons.vfs2.FileNotFoundException;
033import org.apache.commons.vfs2.FileSystemException;
034import org.apache.commons.vfs2.FileSystemOptions;
035import org.apache.commons.vfs2.FileType;
036import org.apache.commons.vfs2.RandomAccessContent;
037import org.apache.commons.vfs2.provider.AbstractFileName;
038import org.apache.commons.vfs2.provider.AbstractFileObject;
039import org.apache.commons.vfs2.provider.URLFileName;
040import org.apache.commons.vfs2.util.MonitorInputStream;
041import org.apache.commons.vfs2.util.RandomAccessMode;
042
043/**
044 * A file object backed by Apache Commons HttpClient.
045 * <p>
046 * TODO - status codes.
047 * </p>
048 *
049 * @param <FS> An {@link HttpFileSystem} subclass
050 * @deprecated Use {@link org.apache.commons.vfs2.provider.http5}.
051 */
052@Deprecated
053public class HttpFileObject<FS extends HttpFileSystem> extends AbstractFileObject<FS> {
054
055    /**
056     * An InputStream that cleans up the HTTP connection on close.
057     */
058    static class HttpInputStream extends MonitorInputStream {
059        private final GetMethod method;
060
061        HttpInputStream(final GetMethod method) throws IOException {
062            super(method.getResponseBodyAsStream());
063            this.method = method;
064        }
065
066        HttpInputStream(final GetMethod method, final int bufferSize) throws IOException {
067            super(method.getResponseBodyAsStream(), bufferSize);
068            this.method = method;
069        }
070
071        /**
072         * Called after the stream has been closed.
073         */
074        @Override
075        protected void onClose() throws IOException {
076            method.releaseConnection();
077        }
078    }
079
080    private final String urlCharset;
081    private final String userAgent;
082    private final boolean followRedirect;
083    private HeadMethod method;
084
085    /**
086     * Constructs a new instance.
087     *
088     * @param fileName the file name.
089     * @param fileSystem the file system.
090     */
091    protected HttpFileObject(final AbstractFileName fileName, final FS fileSystem) {
092        this(fileName, fileSystem, HttpFileSystemConfigBuilder.getInstance());
093    }
094
095    /**
096     * Constructs a new instance.
097     *
098     * @param fileName the file name.
099     * @param fileSystem the file system.
100     * @param builder Configuration options for HTTP.
101     */
102    protected HttpFileObject(final AbstractFileName fileName, final FS fileSystem,
103            final HttpFileSystemConfigBuilder builder) {
104        super(fileName, fileSystem);
105        final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
106        urlCharset = builder.getUrlCharset(fileSystemOptions);
107        userAgent = builder.getUserAgent(fileSystemOptions);
108        followRedirect = builder.getFollowRedirect(fileSystemOptions);
109    }
110
111    /**
112     * Detaches this file object from its file resource.
113     */
114    @Override
115    protected void doDetach() throws Exception {
116        synchronized (getFileSystem()) {
117            method = null;
118        }
119    }
120
121    /**
122     * Returns the size of the file content (in bytes).
123     */
124    @Override
125    protected long doGetContentSize() throws Exception {
126        final Header header = getHeadMethod().getResponseHeader("content-length");
127        if (header == null) {
128            // Assume 0 content-length
129            return 0;
130        }
131        return Long.parseLong(header.getValue());
132    }
133
134    /**
135     * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
136     * {@link FileType#FILE}.
137     * <p>
138     * It is guaranteed that there are no open output streams for this file when this method is called.
139     * </p>
140     * <p>
141     * The returned stream does not have to be buffered.
142     * </p>
143     */
144    @Override
145    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
146        final GetMethod getMethod = new GetMethod();
147        setupMethod(getMethod);
148        final int status = getAbstractFileSystem().getClient().executeMethod(getMethod);
149        if (status == HttpURLConnection.HTTP_NOT_FOUND) {
150            throw new FileNotFoundException(getName());
151        }
152        if (status != HttpURLConnection.HTTP_OK) {
153            throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
154        }
155
156        return new HttpInputStream(getMethod, bufferSize);
157    }
158
159    /**
160     * Returns the last modified time of this file.
161     * <p>
162     * This implementation throws an exception.
163     * </p>
164     */
165    @Override
166    protected long doGetLastModifiedTime() throws Exception {
167        final Header header = getHeadMethod().getResponseHeader("last-modified");
168        FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
169        return DateUtil.parseDate(header.getValue()).getTime();
170    }
171
172    @Override
173    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
174        return new HttpRandomAccessContent<>(this, mode);
175    }
176
177    /**
178     * Determines the type of this file. Must not return null. The return value of this method is cached, so the
179     * implementation can be expensive.
180     */
181    @Override
182    protected FileType doGetType() throws Exception {
183        // Use the HEAD method to probe the file.
184        final int status = this.getHeadMethod().getStatusCode();
185        if (status == HttpURLConnection.HTTP_OK
186                || status == HttpURLConnection.HTTP_BAD_METHOD /* method is bad, but resource exist */) {
187            return FileType.FILE;
188        }
189        if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) {
190            return FileType.IMAGINARY;
191        }
192        throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
193    }
194
195    @Override
196    protected boolean doIsWriteable() throws Exception {
197        return false;
198    }
199
200    /**
201     * Throws UnsupportedOperationException.
202     *
203     * @throws UnsupportedOperationException always thrown.
204     */
205    @Override
206    protected String[] doListChildren() throws Exception {
207        throw new UnsupportedOperationException("Not implemented.");
208    }
209
210    /**
211     * Encodes the given path.
212     *
213     * @param unescaped An unescaped path.
214     * @return the encoded path.
215     * @throws URIException if the default protocol charset is not supported
216     */
217    protected String encodePath(final String unescaped) throws URIException {
218        return URIUtil.encodePath(unescaped);
219    }
220
221    /**
222     * Gets a new FileContentInfoFactory.
223     *
224     * @return a new FileContentInfoFactory.
225     */
226    @Override
227    protected FileContentInfoFactory getFileContentInfoFactory() {
228        return new HttpFileContentInfoFactory();
229    }
230
231    /**
232     * Gets whether to follow redirects.
233     *
234     * @return whether to follow redirects.
235     */
236    protected boolean getFollowRedirect() {
237        return followRedirect;
238    }
239
240    /**
241     * Gets a new HeadMethod.
242     *
243     * @return a new HeadMethod.
244     * @throws IOException if an IO error occurs.
245     */
246    HeadMethod getHeadMethod() throws IOException {
247        // need to synchronize on the file system as the detach method will clear out "method"
248        synchronized (getFileSystem()) {
249            if (method != null) {
250                return method;
251            }
252            method = new HeadMethod();
253            try {
254                setupMethod(method);
255                final HttpClient client = getAbstractFileSystem().getClient();
256                client.executeMethod(method);
257            } finally {
258                method.releaseConnection();
259            }
260            return method;
261        }
262    }
263
264    /**
265     * Gets the URL charset name.
266     *
267     * @return the URL charset name.
268     */
269    protected String getUrlCharset() {
270        return urlCharset;
271    }
272
273    /**
274     * Gets the user agent.
275     *
276     * @return the user agent.
277     */
278    protected String getUserAgent() {
279        return userAgent;
280    }
281
282    /**
283     * Prepares a HttpMethod object.
284     *
285     * @param method The object which gets prepared to access the file object.
286     * @throws FileSystemException if an error occurs.
287     * @throws URIException if path cannot be represented.
288     * @since 2.0 (was package)
289     */
290    protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException {
291        final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset());
292        method.setPath(pathEncoded);
293        method.setFollowRedirects(this.getFollowRedirect());
294        method.setRequestHeader("User-Agent", this.getUserAgent());
295    }
296
297    /*
298     * protected Map doGetAttributes() throws Exception { TreeMap map = new TreeMap();
299     *
300     * Header contentType = method.getResponseHeader("content-type"); if (contentType != null) { HeaderElement[] element
301     * = contentType.getValues(); if (element != null && element.length > 0) { map.put("content-type",
302     * element[0].getName()); } }
303     *
304     * map.put("content-encoding", method.getResponseCharSet()); return map; }
305     */
306}