View Javadoc

1   /* ClientFTP.java
2    *
3    * $Id: ClientFTP.java 6662 2009-11-17 02:27:20Z nlevitt $
4    *
5    * Created on Jun 5, 2003
6    *
7    * Copyright (C) 2003 Internet Archive.
8    *
9    * This file is part of the Heritrix web crawler (crawler.archive.org).
10   *
11   * Heritrix is free software; you can redistribute it and/or modify
12   * it under the terms of the GNU Lesser Public License as published by
13   * the Free Software Foundation; either version 2.1 of the License, or
14   * any later version.
15   *
16   * Heritrix is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU Lesser Public License for more details.
20   *
21   * You should have received a copy of the GNU Lesser Public License
22   * along with Heritrix; if not, write to the Free Software
23   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24   */
25  package org.archive.net;
26  
27  
28  import java.io.BufferedReader;
29  import java.io.IOException;
30  import java.io.Reader;
31  import java.io.StringReader;
32  import java.net.Socket;
33  import java.util.Iterator;
34  import java.util.logging.Level;
35  import java.util.logging.Logger;
36  
37  import org.apache.commons.io.LineIterator;
38  import org.apache.commons.net.ProtocolCommandEvent;
39  import org.apache.commons.net.ProtocolCommandListener;
40  import org.apache.commons.net.ftp.FTPClient;
41  
42  /***
43   * Client for FTP operations. Saves the commands sent to the server and replies
44   * received, which can be retrieved with {@link #getControlConversation()}.
45   * 
46   * @author pjack
47   * @author nlevitt
48   */
49  public class ClientFTP extends FTPClient implements ProtocolCommandListener {
50  
51      private final Logger logger = Logger.getLogger(this.getClass().getName());
52  
53      // Records the conversation on the ftp control channel. The format is based on "curl -v".
54      protected StringBuilder controlConversation;
55      protected Socket dataSocket;
56  
57      /***
58       * Constructs a new <code>ClientFTP</code>.
59       */
60      public ClientFTP() {
61          controlConversation = new StringBuilder();
62          addProtocolCommandListener(this);
63      }
64  
65      /***
66       * Opens a data connection.
67       * 
68       * @param command
69       *            the data command (eg, RETR or LIST)
70       * @param path
71       *            the path of the file to retrieve
72       * @return the socket to read data from, or null if server says not found,
73       *         permission denied, etc
74       * @throws IOException
75       *             if a network error occurs
76       */
77      public Socket openDataConnection(int command, String path)
78      throws IOException {
79          try {
80              dataSocket = _openDataConnection_(command, path);
81              if (dataSocket != null) {
82                  recordAdditionalInfo("Opened data connection to "
83                          + dataSocket.getInetAddress().getHostAddress() + ":"
84                          + dataSocket.getPort());
85              }
86              return dataSocket;
87          } catch (IOException e) {
88              if (getPassiveHost() != null) {
89                  recordAdditionalInfo("Failed to open data connection to "
90                          + getPassiveHost() + ":" + getPassivePort() + ": "
91                          + e.getMessage());
92              } else {
93                  recordAdditionalInfo("Failed to open data connection: "
94                          + e.getMessage());
95              }
96              throw e;
97          }
98      }
99  
100     public void closeDataConnection() {
101         if (dataSocket != null) {
102             String dataHostPort = dataSocket.getInetAddress().getHostAddress()
103                     + ":" + dataSocket.getPort();
104             try {
105                 dataSocket.close();
106                 recordAdditionalInfo("Closed data connection to "
107                         + dataHostPort);
108             } catch (IOException e) {
109                 recordAdditionalInfo("Problem closing data connection to "
110                         + dataHostPort + ": " + e.getMessage());
111             }
112         }
113     }
114 
115     protected void _connectAction_() throws IOException {
116         try {
117             recordAdditionalInfo("Opening control connection to "
118                     + getRemoteAddress().getHostAddress() + ":"
119                     + getRemotePort());
120             super._connectAction_();
121         } catch (IOException e) {
122             recordAdditionalInfo("Failed to open control connection to "
123                     + getRemoteAddress().getHostAddress() + ":"
124                     + getRemotePort() + ": " + e.getMessage());
125             throw e;
126         }
127     }
128     
129     public void disconnect() throws IOException {
130         String remoteHostPort = getRemoteAddress().getHostAddress() + ":"
131                 + getRemotePort();
132         super.disconnect();
133         recordAdditionalInfo("Closed control connection to " + remoteHostPort);
134     }
135 
136     public String getControlConversation() {
137         return controlConversation.toString();
138     }    
139 
140     private class IterableLineIterator extends LineIterator implements Iterable<String> {
141         public IterableLineIterator(final Reader reader) throws IllegalArgumentException {
142             super(reader);
143         }
144         @SuppressWarnings("unchecked")
145         public Iterator<String> iterator() {
146             return this;
147         }
148     }
149     
150     protected void recordControlMessage(String linePrefix, String message) {
151         for (String line: new IterableLineIterator(new BufferedReader(new StringReader(message)))) {
152             controlConversation.append(linePrefix);
153             controlConversation.append(line);
154             controlConversation.append(NETASCII_EOL);
155             if (logger.isLoggable(Level.FINEST)) {
156                 logger.finest(linePrefix + line);
157             }
158         }
159     }
160 
161     public void protocolCommandSent(ProtocolCommandEvent event) {
162         recordControlMessage("> ", event.getMessage());
163     }
164 
165     public void protocolReplyReceived(ProtocolCommandEvent event) {
166         recordControlMessage("< ", event.getMessage());
167     }
168 
169     // for noting things like successful/unsuccessful connection to data port
170     private void recordAdditionalInfo(String message) {
171         recordControlMessage("* ", message);
172     }
173 
174     // XXX see https://issues.apache.org/jira/browse/NET-257
175     @Override
176     public String[] getReplyStrings() {
177         return _replyLines.toArray(new String[0]);
178     }
179 }