src/org/sonews/storage/Article.java
author František Kučera <franta-hg@frantovo.cz>
Wed Dec 31 12:07:40 2014 +0100 (2014-12-31)
changeset 120 bb1c8a7b774c
parent 101 d54786065fa3
permissions -rwxr-xr-x
XSLT: licence – GNU GPLv3
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     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.
     9  *
    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.
    14  *
    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/>.
    17  */
    18 package org.sonews.storage;
    19 
    20 import java.io.ByteArrayInputStream;
    21 import java.io.ByteArrayOutputStream;
    22 import java.io.IOException;
    23 import java.security.MessageDigest;
    24 import java.security.NoSuchAlgorithmException;
    25 import java.util.UUID;
    26 import java.util.ArrayList;
    27 import java.util.Enumeration;
    28 import java.util.List;
    29 import java.util.logging.Level;
    30 import javax.mail.Header;
    31 import javax.mail.Message;
    32 import javax.mail.MessagingException;
    33 import javax.mail.internet.InternetHeaders;
    34 import org.sonews.acl.User;
    35 import org.sonews.config.Config;
    36 import org.sonews.util.Log;
    37 
    38 /**
    39  * Represents a newsgroup article.
    40  * @author Christian Lins
    41  * @author Denis Schwerdel
    42  * @since n3tpd/0.1
    43  */
    44 public class Article extends ArticleHead {
    45 	
    46 	private User sender;
    47 
    48 	/**
    49 	 * Loads the Article identified by the given ID from the JDBCDatabase.
    50 	 * @param messageID
    51 	 * @return null if Article is not found or if an error occurred.
    52 	 */
    53 	public static Article getByMessageID(final String messageID) {
    54 		try {
    55 			return StorageManager.current().getArticle(messageID);
    56 		} catch (StorageBackendException ex) {
    57 			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
    58 			return null;
    59 		}
    60 	}
    61 	private byte[] body = new byte[0];
    62 
    63 	/**
    64 	 * Default constructor.
    65 	 */
    66 	public Article() {
    67 	}
    68 
    69 	/**
    70 	 * Creates a new Article object using the date from the given
    71 	 * raw data.
    72 	 */
    73 	public Article(String headers, byte[] body) {
    74 		try {
    75 			this.body = body;
    76 
    77 			// Parse the header
    78 			this.headers = new InternetHeaders(
    79 					new ByteArrayInputStream(headers.getBytes()));
    80 
    81 			this.headerSrc = headers;
    82 		} catch (MessagingException ex) {
    83 			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
    84 		}
    85 	}
    86 
    87 	/**
    88 	 * Creates an Article instance using the data from the javax.mail.Message
    89 	 * object. This constructor is called by the Mailinglist gateway.
    90 	 * @see javax.mail.Message
    91 	 * @param msg
    92 	 * @throws IOException
    93 	 * @throws MessagingException
    94 	 */
    95 	public Article(final Message msg)
    96 			throws IOException, MessagingException {
    97 		this.headers = new InternetHeaders();
    98 
    99 		for (Enumeration e = msg.getAllHeaders(); e.hasMoreElements();) {
   100 			final Header header = (Header) e.nextElement();
   101 			this.headers.addHeader(header.getName(), header.getValue());
   102 		}
   103 
   104 		// Reads the raw byte body using Message.writeTo(OutputStream out)
   105 		this.body = readContent(msg);
   106 
   107 		// Validate headers
   108 		validateHeaders();
   109 	}
   110 
   111 	/**
   112 	 * Reads from the given Message into a byte array.
   113 	 * @param in
   114 	 * @return
   115 	 * @throws IOException
   116 	 */
   117 	private byte[] readContent(Message in)
   118 			throws IOException, MessagingException {
   119 		ByteArrayOutputStream out = new ByteArrayOutputStream();
   120 		in.writeTo(out);
   121 		return out.toByteArray();
   122 	}
   123 
   124 	/**
   125 	 * Removes the header identified by the given key.
   126 	 * @param headerKey
   127 	 */
   128 	public void removeHeader(final String headerKey) {
   129 		this.headers.removeHeader(headerKey);
   130 		this.headerSrc = null;
   131 	}
   132 
   133 	/**
   134 	 * Generates a message id for this article and sets it into
   135 	 * the header object. You have to update the JDBCDatabase manually to make this
   136 	 * change persistent.
   137 	 * Note: a Message-ID should never be changed and only generated once.
   138 	 */
   139 	private String generateMessageID() {
   140 		String randomString;
   141 		MessageDigest md5;
   142 		try {
   143 			md5 = MessageDigest.getInstance("MD5");
   144 			md5.reset();
   145 			md5.update(getBody());
   146 			md5.update(getHeader(Headers.SUBJECT)[0].getBytes());
   147 			md5.update(getHeader(Headers.FROM)[0].getBytes());
   148 			byte[] result = md5.digest();
   149 			StringBuilder hexString = new StringBuilder();
   150 			for (int i = 0; i < result.length; i++) {
   151 				hexString.append(Integer.toHexString(0xFF & result[i]));
   152 			}
   153 			randomString = hexString.toString();
   154 		} catch (NoSuchAlgorithmException ex) {
   155 			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
   156 			randomString = UUID.randomUUID().toString();
   157 		}
   158 		String msgID = "<" + randomString + "@"
   159 				+ Config.inst().get(Config.HOSTNAME, "localhost") + ">";
   160 
   161 		this.headers.setHeader(Headers.MESSAGE_ID, msgID);
   162 
   163 		return msgID;
   164 	}
   165 
   166 	/**
   167 	 * Returns the body string.
   168 	 */
   169 	public byte[] getBody() {
   170 		return body;
   171 	}
   172 
   173 	/**
   174 	 * @return Numerical IDs of the newsgroups this Article belongs to.
   175 	 */
   176 	public List<Group> getGroups() {
   177 		String[] groupnames = getHeader(Headers.NEWSGROUPS)[0].split(",");
   178 		ArrayList<Group> groups = new ArrayList<Group>();
   179 
   180 		try {
   181 			for (String newsgroup : groupnames) {
   182 				newsgroup = newsgroup.trim();
   183 				Group group = StorageManager.current().getGroup(newsgroup);
   184 				if (group != null && // If the server does not provide the group, ignore it
   185 						!groups.contains(group)) // Yes, there may be duplicates
   186 				{
   187 					groups.add(group);
   188 				}
   189 			}
   190 		} catch (StorageBackendException ex) {
   191 			Log.get().log(Level.WARNING, ex.getLocalizedMessage(), ex);
   192 			return null;
   193 		}
   194 		return groups;
   195 	}
   196 
   197 	public void setBody(byte[] body) {
   198 		this.body = body;
   199 	}
   200 
   201 	/**
   202 	 *
   203 	 * @param groupname Name(s) of newsgroups
   204 	 */
   205 	public void setGroup(String groupname) {
   206 		this.headers.setHeader(Headers.NEWSGROUPS, groupname);
   207 	}
   208 
   209 	/**
   210 	 * Returns the Message-ID of this Article. If the appropriate header
   211 	 * is empty, a new Message-ID is created.
   212 	 * @return Message-ID of this Article.
   213 	 */
   214 	public String getMessageID() {
   215 		String[] msgID = getHeader(Headers.MESSAGE_ID);
   216 		return msgID[0].equals("") ? generateMessageID() : msgID[0];
   217 	}
   218 
   219 	/**
   220 	 * @return String containing the Message-ID.
   221 	 */
   222 	@Override
   223 	public String toString() {
   224 		return getMessageID();
   225 	}
   226 
   227 	/**
   228 	 * @return sender – currently logged user – or null, if user is not authenticated.
   229 	 */
   230 	public User getUser() {
   231 		return sender;
   232 	}
   233 	
   234 	/**
   235 	 * This method is to be called from POST Command implementation.
   236 	 * @param sender current username – or null, if user is not authenticated.
   237 	 */
   238 	public void setUser(User sender) {
   239 		this.sender = sender;
   240 	}
   241 }