1.1 --- a/src/org/sonews/daemon/command/PostCommand.java Sun Aug 29 17:43:58 2010 +0200
1.2 +++ b/src/org/sonews/daemon/command/PostCommand.java Sun Aug 29 18:17:37 2010 +0200
1.3 @@ -47,286 +47,237 @@
1.4 */
1.5 public class PostCommand implements Command
1.6 {
1.7 -
1.8 - private final Article article = new Article();
1.9 - private int lineCount = 0;
1.10 - private long bodySize = 0;
1.11 - private InternetHeaders headers = null;
1.12 - private long maxBodySize =
1.13 - Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
1.14 - private PostState state = PostState.WaitForLineOne;
1.15 - private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
1.16 - private final StringBuilder strHead = new StringBuilder();
1.17
1.18 - @Override
1.19 - public String[] getSupportedCommandStrings()
1.20 - {
1.21 - return new String[]{"POST"};
1.22 - }
1.23 + private final Article article = new Article();
1.24 + private int lineCount = 0;
1.25 + private long bodySize = 0;
1.26 + private InternetHeaders headers = null;
1.27 + private long maxBodySize =
1.28 + Config.inst().get(Config.ARTICLE_MAXSIZE, 128) * 1024L; // Size in bytes
1.29 + private PostState state = PostState.WaitForLineOne;
1.30 + private final ByteArrayOutputStream bufBody = new ByteArrayOutputStream();
1.31 + private final StringBuilder strHead = new StringBuilder();
1.32
1.33 - @Override
1.34 - public boolean hasFinished()
1.35 - {
1.36 - return this.state == PostState.Finished;
1.37 - }
1.38 + @Override
1.39 + public String[] getSupportedCommandStrings()
1.40 + {
1.41 + return new String[] {"POST"};
1.42 + }
1.43
1.44 - @Override
1.45 - public String impliedCapability()
1.46 - {
1.47 - return null;
1.48 - }
1.49 + @Override
1.50 + public boolean hasFinished()
1.51 + {
1.52 + return this.state == PostState.Finished;
1.53 + }
1.54
1.55 - @Override
1.56 - public boolean isStateful()
1.57 - {
1.58 - return true;
1.59 - }
1.60 + @Override
1.61 + public String impliedCapability()
1.62 + {
1.63 + return null;
1.64 + }
1.65
1.66 - /**
1.67 - * Process the given line String. line.trim() was called by NNTPConnection.
1.68 - * @param line
1.69 - * @throws java.io.IOException
1.70 - * @throws java.sql.SQLException
1.71 - */
1.72 - @Override // TODO: Refactor this method to reduce complexity!
1.73 - public void processLine(NNTPConnection conn, String line, byte[] raw)
1.74 - throws IOException, StorageBackendException
1.75 - {
1.76 - switch(state)
1.77 - {
1.78 - case WaitForLineOne:
1.79 - {
1.80 - if(line.equalsIgnoreCase("POST"))
1.81 - {
1.82 - conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
1.83 - state = PostState.ReadingHeaders;
1.84 - }
1.85 - else
1.86 - {
1.87 - conn.println("500 invalid command usage");
1.88 - }
1.89 - break;
1.90 - }
1.91 - case ReadingHeaders:
1.92 - {
1.93 - strHead.append(line);
1.94 - strHead.append(NNTPConnection.NEWLINE);
1.95 -
1.96 - if("".equals(line) || ".".equals(line))
1.97 - {
1.98 - // we finally met the blank line
1.99 - // separating headers from body
1.100 -
1.101 - try
1.102 - {
1.103 - // Parse the header using the InternetHeader class from JavaMail API
1.104 - headers = new InternetHeaders(
1.105 - new ByteArrayInputStream(strHead.toString().trim()
1.106 - .getBytes(conn.getCurrentCharset())));
1.107 + @Override
1.108 + public boolean isStateful()
1.109 + {
1.110 + return true;
1.111 + }
1.112
1.113 - // add the header entries for the article
1.114 - article.setHeaders(headers);
1.115 - }
1.116 - catch (MessagingException e)
1.117 - {
1.118 - e.printStackTrace();
1.119 - conn.println("500 posting failed - invalid header");
1.120 - state = PostState.Finished;
1.121 - break;
1.122 - }
1.123 + /**
1.124 + * Process the given line String. line.trim() was called by NNTPConnection.
1.125 + * @param line
1.126 + * @throws java.io.IOException
1.127 + * @throws java.sql.SQLException
1.128 + */
1.129 + @Override // TODO: Refactor this method to reduce complexity!
1.130 + public void processLine(NNTPConnection conn, String line, byte[] raw)
1.131 + throws IOException, StorageBackendException
1.132 + {
1.133 + switch (state) {
1.134 + case WaitForLineOne: {
1.135 + if (line.equalsIgnoreCase("POST")) {
1.136 + conn.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
1.137 + state = PostState.ReadingHeaders;
1.138 + } else {
1.139 + conn.println("500 invalid command usage");
1.140 + }
1.141 + break;
1.142 + }
1.143 + case ReadingHeaders: {
1.144 + strHead.append(line);
1.145 + strHead.append(NNTPConnection.NEWLINE);
1.146
1.147 - // Change charset for reading body;
1.148 - // for multipart messages UTF-8 is returned
1.149 - //conn.setCurrentCharset(article.getBodyCharset());
1.150 -
1.151 - state = PostState.ReadingBody;
1.152 -
1.153 - if(".".equals(line))
1.154 - {
1.155 - // Post an article without body
1.156 - postArticle(conn, article);
1.157 - state = PostState.Finished;
1.158 - }
1.159 - }
1.160 - break;
1.161 - }
1.162 - case ReadingBody:
1.163 - {
1.164 - if(".".equals(line))
1.165 - {
1.166 - // Set some headers needed for Over command
1.167 - headers.setHeader(Headers.LINES, Integer.toString(lineCount));
1.168 - headers.setHeader(Headers.BYTES, Long.toString(bodySize));
1.169 + if ("".equals(line) || ".".equals(line)) {
1.170 + // we finally met the blank line
1.171 + // separating headers from body
1.172
1.173 - byte[] body = bufBody.toByteArray();
1.174 - if(body.length >= 2)
1.175 - {
1.176 - // Remove trailing CRLF
1.177 - body = Arrays.copyOf(body, body.length - 2);
1.178 - }
1.179 - article.setBody(body); // set the article body
1.180 -
1.181 - postArticle(conn, article);
1.182 - state = PostState.Finished;
1.183 - }
1.184 - else
1.185 - {
1.186 - bodySize += line.length() + 1;
1.187 - lineCount++;
1.188 -
1.189 - // Add line to body buffer
1.190 - bufBody.write(raw, 0, raw.length);
1.191 - bufBody.write(NNTPConnection.NEWLINE.getBytes());
1.192 -
1.193 - if(bodySize > maxBodySize)
1.194 - {
1.195 - conn.println("500 article is too long");
1.196 - state = PostState.Finished;
1.197 - break;
1.198 - }
1.199 - }
1.200 - break;
1.201 - }
1.202 - default:
1.203 - {
1.204 - // Should never happen
1.205 - Log.get().severe("PostCommand::processLine(): already finished...");
1.206 - }
1.207 - }
1.208 - }
1.209 -
1.210 - /**
1.211 - * Article is a control message and needs special handling.
1.212 - * @param article
1.213 - */
1.214 - private void controlMessage(NNTPConnection conn, Article article)
1.215 - throws IOException
1.216 - {
1.217 - String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
1.218 - if(ctrl.length == 2) // "cancel <mid>"
1.219 - {
1.220 - try
1.221 - {
1.222 - StorageManager.current().delete(ctrl[1]);
1.223 -
1.224 - // Move cancel message to "control" group
1.225 - article.setHeader(Headers.NEWSGROUPS, "control");
1.226 - StorageManager.current().addArticle(article);
1.227 - conn.println("240 article cancelled");
1.228 - }
1.229 - catch(StorageBackendException ex)
1.230 - {
1.231 - Log.get().severe(ex.toString());
1.232 - conn.println("500 internal server error");
1.233 - }
1.234 - }
1.235 - else
1.236 - {
1.237 - conn.println("441 unknown control header");
1.238 - }
1.239 - }
1.240 -
1.241 - private void supersedeMessage(NNTPConnection conn, Article article)
1.242 - throws IOException
1.243 - {
1.244 - try
1.245 - {
1.246 - String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
1.247 - StorageManager.current().delete(oldMsg);
1.248 - StorageManager.current().addArticle(article);
1.249 - conn.println("240 article replaced");
1.250 - }
1.251 - catch(StorageBackendException ex)
1.252 - {
1.253 - Log.get().severe(ex.toString());
1.254 - conn.println("500 internal server error");
1.255 - }
1.256 - }
1.257 -
1.258 - private void postArticle(NNTPConnection conn, Article article)
1.259 - throws IOException
1.260 - {
1.261 - if(article.getHeader(Headers.CONTROL)[0].length() > 0)
1.262 - {
1.263 - controlMessage(conn, article);
1.264 - }
1.265 - else if(article.getHeader(Headers.SUPERSEDES)[0].length() > 0)
1.266 - {
1.267 - supersedeMessage(conn, article);
1.268 - }
1.269 - else // Post the article regularily
1.270 - {
1.271 - // Circle check; note that Path can already contain the hostname here
1.272 - String host = Config.inst().get(Config.HOSTNAME, "localhost");
1.273 - if(article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0)
1.274 - {
1.275 - Log.get().info(article.getMessageID() + " skipped for host " + host);
1.276 - conn.println("441 I know this article already");
1.277 - return;
1.278 - }
1.279 + try {
1.280 + // Parse the header using the InternetHeader class from JavaMail API
1.281 + headers = new InternetHeaders(
1.282 + new ByteArrayInputStream(strHead.toString().trim().getBytes(conn.getCurrentCharset())));
1.283
1.284 - // Try to create the article in the database or post it to
1.285 - // appropriate mailing list
1.286 - try
1.287 - {
1.288 - boolean success = false;
1.289 - String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
1.290 - for(String groupname : groupnames)
1.291 - {
1.292 - Group group = StorageManager.current().getGroup(groupname);
1.293 - if(group != null && !group.isDeleted())
1.294 - {
1.295 - if(group.isMailingList() && !conn.isLocalConnection())
1.296 - {
1.297 - // Send to mailing list; the Dispatcher writes
1.298 - // statistics to database
1.299 - Dispatcher.toList(article, group.getName());
1.300 - success = true;
1.301 - }
1.302 - else
1.303 - {
1.304 - // Store in database
1.305 - if(!StorageManager.current().isArticleExisting(article.getMessageID()))
1.306 - {
1.307 - StorageManager.current().addArticle(article);
1.308 + // add the header entries for the article
1.309 + article.setHeaders(headers);
1.310 + } catch (MessagingException e) {
1.311 + e.printStackTrace();
1.312 + conn.println("500 posting failed - invalid header");
1.313 + state = PostState.Finished;
1.314 + break;
1.315 + }
1.316
1.317 - // Log this posting to statistics
1.318 - Stats.getInstance().mailPosted(
1.319 - article.getHeader(Headers.NEWSGROUPS)[0]);
1.320 - }
1.321 - success = true;
1.322 - }
1.323 - }
1.324 - } // end for
1.325 + // Change charset for reading body;
1.326 + // for multipart messages UTF-8 is returned
1.327 + //conn.setCurrentCharset(article.getBodyCharset());
1.328
1.329 - if(success)
1.330 - {
1.331 - conn.println("240 article posted ok");
1.332 - FeedManager.queueForPush(article);
1.333 - }
1.334 - else
1.335 - {
1.336 - conn.println("441 newsgroup not found");
1.337 - }
1.338 - }
1.339 - catch(AddressException ex)
1.340 - {
1.341 - Log.get().warning(ex.getMessage());
1.342 - conn.println("441 invalid sender address");
1.343 - }
1.344 - catch(MessagingException ex)
1.345 - {
1.346 - // A MessageException is thrown when the sender email address is
1.347 - // invalid or something is wrong with the SMTP server.
1.348 - System.err.println(ex.getLocalizedMessage());
1.349 - conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
1.350 - }
1.351 - catch(StorageBackendException ex)
1.352 - {
1.353 - ex.printStackTrace();
1.354 - conn.println("500 internal server error");
1.355 - }
1.356 - }
1.357 - }
1.358 + state = PostState.ReadingBody;
1.359
1.360 + if (".".equals(line)) {
1.361 + // Post an article without body
1.362 + postArticle(conn, article);
1.363 + state = PostState.Finished;
1.364 + }
1.365 + }
1.366 + break;
1.367 + }
1.368 + case ReadingBody: {
1.369 + if (".".equals(line)) {
1.370 + // Set some headers needed for Over command
1.371 + headers.setHeader(Headers.LINES, Integer.toString(lineCount));
1.372 + headers.setHeader(Headers.BYTES, Long.toString(bodySize));
1.373 +
1.374 + byte[] body = bufBody.toByteArray();
1.375 + if (body.length >= 2) {
1.376 + // Remove trailing CRLF
1.377 + body = Arrays.copyOf(body, body.length - 2);
1.378 + }
1.379 + article.setBody(body); // set the article body
1.380 +
1.381 + postArticle(conn, article);
1.382 + state = PostState.Finished;
1.383 + } else {
1.384 + bodySize += line.length() + 1;
1.385 + lineCount++;
1.386 +
1.387 + // Add line to body buffer
1.388 + bufBody.write(raw, 0, raw.length);
1.389 + bufBody.write(NNTPConnection.NEWLINE.getBytes());
1.390 +
1.391 + if (bodySize > maxBodySize) {
1.392 + conn.println("500 article is too long");
1.393 + state = PostState.Finished;
1.394 + break;
1.395 + }
1.396 + }
1.397 + break;
1.398 + }
1.399 + default: {
1.400 + // Should never happen
1.401 + Log.get().severe("PostCommand::processLine(): already finished...");
1.402 + }
1.403 + }
1.404 + }
1.405 +
1.406 + /**
1.407 + * Article is a control message and needs special handling.
1.408 + * @param article
1.409 + */
1.410 + private void controlMessage(NNTPConnection conn, Article article)
1.411 + throws IOException
1.412 + {
1.413 + String[] ctrl = article.getHeader(Headers.CONTROL)[0].split(" ");
1.414 + if (ctrl.length == 2) // "cancel <mid>"
1.415 + {
1.416 + try {
1.417 + StorageManager.current().delete(ctrl[1]);
1.418 +
1.419 + // Move cancel message to "control" group
1.420 + article.setHeader(Headers.NEWSGROUPS, "control");
1.421 + StorageManager.current().addArticle(article);
1.422 + conn.println("240 article cancelled");
1.423 + } catch (StorageBackendException ex) {
1.424 + Log.get().severe(ex.toString());
1.425 + conn.println("500 internal server error");
1.426 + }
1.427 + } else {
1.428 + conn.println("441 unknown control header");
1.429 + }
1.430 + }
1.431 +
1.432 + private void supersedeMessage(NNTPConnection conn, Article article)
1.433 + throws IOException
1.434 + {
1.435 + try {
1.436 + String oldMsg = article.getHeader(Headers.SUPERSEDES)[0];
1.437 + StorageManager.current().delete(oldMsg);
1.438 + StorageManager.current().addArticle(article);
1.439 + conn.println("240 article replaced");
1.440 + } catch (StorageBackendException ex) {
1.441 + Log.get().severe(ex.toString());
1.442 + conn.println("500 internal server error");
1.443 + }
1.444 + }
1.445 +
1.446 + private void postArticle(NNTPConnection conn, Article article)
1.447 + throws IOException
1.448 + {
1.449 + if (article.getHeader(Headers.CONTROL)[0].length() > 0) {
1.450 + controlMessage(conn, article);
1.451 + } else if (article.getHeader(Headers.SUPERSEDES)[0].length() > 0) {
1.452 + supersedeMessage(conn, article);
1.453 + } else // Post the article regularily
1.454 + {
1.455 + // Circle check; note that Path can already contain the hostname here
1.456 + String host = Config.inst().get(Config.HOSTNAME, "localhost");
1.457 + if (article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0) {
1.458 + Log.get().info(article.getMessageID() + " skipped for host " + host);
1.459 + conn.println("441 I know this article already");
1.460 + return;
1.461 + }
1.462 +
1.463 + // Try to create the article in the database or post it to
1.464 + // appropriate mailing list
1.465 + try {
1.466 + boolean success = false;
1.467 + String[] groupnames = article.getHeader(Headers.NEWSGROUPS)[0].split(",");
1.468 + for (String groupname : groupnames) {
1.469 + Group group = StorageManager.current().getGroup(groupname);
1.470 + if (group != null && !group.isDeleted()) {
1.471 + if (group.isMailingList() && !conn.isLocalConnection()) {
1.472 + // Send to mailing list; the Dispatcher writes
1.473 + // statistics to database
1.474 + Dispatcher.toList(article, group.getName());
1.475 + success = true;
1.476 + } else {
1.477 + // Store in database
1.478 + if (!StorageManager.current().isArticleExisting(article.getMessageID())) {
1.479 + StorageManager.current().addArticle(article);
1.480 +
1.481 + // Log this posting to statistics
1.482 + Stats.getInstance().mailPosted(
1.483 + article.getHeader(Headers.NEWSGROUPS)[0]);
1.484 + }
1.485 + success = true;
1.486 + }
1.487 + }
1.488 + } // end for
1.489 +
1.490 + if (success) {
1.491 + conn.println("240 article posted ok");
1.492 + FeedManager.queueForPush(article);
1.493 + } else {
1.494 + conn.println("441 newsgroup not found");
1.495 + }
1.496 + } catch (AddressException ex) {
1.497 + Log.get().warning(ex.getMessage());
1.498 + conn.println("441 invalid sender address");
1.499 + } catch (MessagingException ex) {
1.500 + // A MessageException is thrown when the sender email address is
1.501 + // invalid or something is wrong with the SMTP server.
1.502 + System.err.println(ex.getLocalizedMessage());
1.503 + conn.println("441 " + ex.getClass().getCanonicalName() + ": " + ex.getLocalizedMessage());
1.504 + } catch (StorageBackendException ex) {
1.505 + ex.printStackTrace();
1.506 + conn.println("500 internal server error");
1.507 + }
1.508 + }
1.509 + }
1.510 }