src/org/sonews/daemon/command/PostCommand.java
changeset 37 74139325d305
parent 35 ed84c8bdd87b
child 42 7f84f4de2893
     1.1 --- a/src/org/sonews/daemon/command/PostCommand.java	Sun Aug 29 17:28: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  }