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) {
143 conn.println("412 no newsgroup selected");
145 String[] command = line.split(" ");
147 // If no parameter was specified, show information about
148 // the currently selected article(s)
149 if (command.length == 1) {
150 final Article art = conn.getCurrentArticle();
152 conn.println("420 no article(s) selected");
156 conn.println(buildOverview(art, -1));
157 } // otherwise print information about the specified range
160 long artEnd = conn.getCurrentChannel().getLastArticleNumber();
161 String[] nums = command[1].split("-");
162 if (nums.length >= 1) {
164 artStart = Integer.parseInt(nums[0]);
165 } catch (NumberFormatException e) {
166 Log.get().info(e.getMessage());
167 artStart = Integer.parseInt(command[1]);
170 artStart = conn.getCurrentChannel().getFirstArticleNumber();
173 if (nums.length >= 2) {
175 artEnd = Integer.parseInt(nums[1]);
176 } catch (NumberFormatException e) {
181 if (artStart > artEnd) {
182 if (command[0].equalsIgnoreCase("OVER")) {
183 conn.println("423 no articles in that range");
185 conn.println("224 (empty) overview information follows:");
189 for (long n = artStart; n <= artEnd; n += MAX_LINES_PER_DBREQUEST) {
190 long nEnd = Math.min(n + MAX_LINES_PER_DBREQUEST - 1, artEnd);
191 List<Pair<Long, ArticleHead>> articleHeads = conn.getCurrentChannel().getArticleHeads(n, nEnd);
192 if (articleHeads.isEmpty() && n == artStart
193 && command[0].equalsIgnoreCase("OVER")) {
194 // This reply is only valid for OVER, not for XOVER command
195 conn.println("423 no articles in that range");
197 } else if (n == artStart) {
198 // XOVER replies this although there is no data available
199 conn.println("224 overview information follows");
202 for (Pair<Long, ArticleHead> article : articleHeads) {
203 String overview = buildOverview(article.getB(), article.getA());
204 conn.println(overview);
213 private String buildOverview(ArticleHead art, long nr)
215 StringBuilder overview = new StringBuilder();
217 overview.append('\t');
219 String subject = art.getHeader(Headers.SUBJECT)[0];
220 if ("".equals(subject)) {
223 overview.append(escapeString(subject));
224 overview.append('\t');
226 overview.append(escapeString(art.getHeader(Headers.FROM)[0]));
227 overview.append('\t');
228 overview.append(escapeString(art.getHeader(Headers.DATE)[0]));
229 overview.append('\t');
230 overview.append(escapeString(art.getHeader(Headers.MESSAGE_ID)[0]));
231 overview.append('\t');
232 overview.append(escapeString(art.getHeader(Headers.REFERENCES)[0]));
233 overview.append('\t');
235 String bytes = art.getHeader(Headers.BYTES)[0];
236 if ("".equals(bytes)) {
239 overview.append(escapeString(bytes));
240 overview.append('\t');
242 String lines = art.getHeader(Headers.LINES)[0];
243 if ("".equals(lines)) {
246 overview.append(escapeString(lines));
247 overview.append('\t');
248 overview.append(escapeString(art.getHeader(Headers.XREF)[0]));
250 // Remove trailing tabs if some data is empty
251 return overview.toString().trim();
254 private String escapeString(String str)
256 String nstr = str.replace("\r", "");
257 nstr = nstr.replace('\n', ' ');
258 nstr = nstr.replace('\t', ' ');