3 * see AUTHORS for the list of contributors
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package org.sonews.daemon.command;
21 import java.io.IOException;
22 import java.util.List;
23 import org.sonews.util.Log;
24 import org.sonews.daemon.NNTPConnection;
25 import org.sonews.storage.Article;
26 import org.sonews.storage.ArticleHead;
27 import org.sonews.storage.Headers;
28 import org.sonews.storage.StorageBackendException;
29 import org.sonews.util.Pair;
32 * Class handling the OVER/XOVER command.
34 * Description of the XOVER command:
38 * The XOVER command returns information from the overview
39 * database for the article(s) specified.
41 * The optional range argument may be any of the following:
43 * an article number followed by a dash to indicate
45 * an article number followed by a dash followed by
46 * another article number
48 * If no argument is specified, then information from the
49 * current article is displayed. Successful responses start
50 * with a 224 response followed by the overview information
51 * for all matched messages. Once the output is complete, a
52 * period is sent on a line by itself. If no argument is
53 * specified, the information for the current article is
54 * returned. A news group must have been selected earlier,
55 * else a 412 error response is returned. If no articles are
56 * in the range specified, a 420 error response is returned
57 * by the server. A 502 response will be returned if the
58 * client only has permission to transfer articles.
60 * Each line of output will be formatted with the article number,
61 * followed by each of the headers in the overview database or the
62 * article itself (when the data is not available in the overview
63 * database) for that article separated by a tab character. The
64 * sequence of fields must be in this order: subject, author,
65 * date, message-id, references, byte count, and line count. Other
66 * optional fields may follow line count. Other optional fields may
67 * follow line count. These fields are specified by examining the
68 * response to the LIST OVERVIEW.FMT command. Where no data exists,
69 * a null field must be provided (i.e. the output will have two tab
70 * characters adjacent to each other). Servers should not output
71 * fields for articles that have been removed since the XOVER database
74 * The LIST OVERVIEW.FMT command should be implemented if XOVER
75 * is implemented. A client can use LIST OVERVIEW.FMT to determine
76 * what optional fields and in which order all fields will be
77 * supplied by the XOVER command.
79 * Note that any tab and end-of-line characters in any header
80 * data that is returned will be converted to a space character.
84 * 224 Overview information follows
85 * 412 No news group current selected
86 * 420 No article(s) selected
89 * OVER defines additional responses:
91 * First form (message-id specified)
92 * 224 Overview information follows (multi-line)
93 * 430 No article with that message-id
95 * Second form (range specified)
96 * 224 Overview information follows (multi-line)
97 * 412 No newsgroup selected
98 * 423 No articles in that range
100 * Third form (current article number used)
101 * 224 Overview information follows (multi-line)
102 * 412 No newsgroup selected
103 * 420 Current article number is invalid
106 * @author Christian Lins
107 * @since sonews/0.5.0
109 public class OverCommand implements Command
112 public static final int MAX_LINES_PER_DBREQUEST = 200;
115 public String[] getSupportedCommandStrings()
117 return new String[]{"OVER", "XOVER"};
121 public boolean hasFinished()
127 public String impliedCapability()
133 public boolean isStateful()
139 public void processLine(NNTPConnection conn, final String line, byte[] raw)
140 throws IOException, StorageBackendException
142 if(conn.getCurrentChannel() == null)
144 conn.println("412 no newsgroup selected");
148 String[] command = line.split(" ");
150 // If no parameter was specified, show information about
151 // the currently selected article(s)
152 if(command.length == 1)
154 final Article art = conn.getCurrentArticle();
157 conn.println("420 no article(s) selected");
161 conn.println(buildOverview(art, -1));
163 // otherwise print information about the specified range
167 long artEnd = conn.getCurrentChannel().getLastArticleNumber();
168 String[] nums = command[1].split("-");
173 artStart = Integer.parseInt(nums[0]);
175 catch(NumberFormatException e)
177 Log.get().info(e.getMessage());
178 artStart = Integer.parseInt(command[1]);
183 artStart = conn.getCurrentChannel().getFirstArticleNumber();
190 artEnd = Integer.parseInt(nums[1]);
192 catch(NumberFormatException e)
198 if(artStart > artEnd)
200 if(command[0].equalsIgnoreCase("OVER"))
202 conn.println("423 no articles in that range");
206 conn.println("224 (empty) overview information follows:");
212 for(long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST)
214 long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
215 List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel()
216 .getArticleHeads(n, nEnd);
217 if(articleHeads.isEmpty() && n == artStart
218 && command[0].equalsIgnoreCase("OVER"))
220 // This reply is only valid for OVER, not for XOVER command
221 conn.println("423 no articles in that range");
224 else if(n == artStart)
226 // XOVER replies this although there is no data available
227 conn.println("224 overview information follows");
230 for(Pair<Long, ArticleHead> article : articleHeads)
232 String overview = buildOverview(article.getB(), article.getA());
233 conn.println(overview);
242 private String buildOverview(ArticleHead art, long nr)
244 StringBuilder overview = new StringBuilder();
246 overview.append('\t');
248 String subject = art.getHeader(Headers.SUBJECT)[0];
249 if("".equals(subject))
253 overview.append(escapeString(subject));
254 overview.append('\t');
256 overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
257 overview.append('\t');
258 overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
259 overview.append('\t');
260 overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
261 overview.append('\t');
262 overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
263 overview.append('\t');
265 String bytes = art.getHeader(Headers.BYTES)[0];
270 overview.append(escapeString(bytes));
271 overview.append('\t');
273 String lines = art.getHeader(Headers.LINES)[0];
278 overview.append(escapeString(lines));
279 overview.append('\t');
280 overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
282 // Remove trailing tabs if some data is empty
283 return overview.toString().trim();
286 private String escapeString(String str)
288 String nstr = str.replace("\r", "");
289 nstr = nstr.replace('\n', ' ');
290 nstr = nstr.replace('\t', ' ');