src/org/sonews/storage/Article.java
author František Kučera <franta-hg@frantovo.cz>
Sun Nov 06 00:08:05 2011 +0100 (2011-11-06)
changeset 116 4ddc1020a154
parent 51 be419cf170d6
child 117 79ce65d63cce
permissions -rwxr-xr-x
Podpora Markdown syntaxe
na začátek odesílané zprávy stačí přidat:
#!markdown
a zpráva se pak prožene Markdown procesorem (spouštěno přes sudo pod jiným uživatelem)
a pak teprve přes standardní XSL transformaci.

1) Vytvořit uživatele a skupinu markdown

2) Do /etc/sudoers přidat:
Cmnd_Alias MARKDOWN = /usr/bin/markdown
%markdown ALL = (%markdown) NOPASSWD : MARKDOWN

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