From a651e8ef73884efbe750efb857df0b83ac5278db Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Sat, 2 Mar 2013 04:45:02 +0000
Subject: [PATCH] Removed subject line from wire format, added content type.

---
 .../res/drawable-hdpi/content_attachment.png  | Bin 0 -> 1502 bytes
 .../res/drawable-mdpi/content_attachment.png  | Bin 0 -> 1318 bytes
 .../res/drawable-xhdpi/content_attachment.png | Bin 0 -> 1717 bytes
 .../android/messages/ConversationAdapter.java |   7 ++
 .../messages/ConversationListActivity.java    |  14 +--
 .../sf/briar/api/db/GroupMessageHeader.java   |   8 +-
 .../net/sf/briar/api/db/MessageHeader.java    |  12 ++-
 .../sf/briar/api/db/PrivateMessageHeader.java |   8 +-
 .../net/sf/briar/api/messaging/Message.java   |   9 +-
 .../briar/api/messaging/MessageFactory.java   |  14 +--
 .../api/messaging/MessagingConstants.java     |   3 +
 .../api/messaging/UnverifiedMessage.java      |  20 +++-
 .../sf/briar/db/DatabaseComponentImpl.java    |   2 +-
 .../src/net/sf/briar/db/JdbcDatabase.java     | 101 ++++++++++--------
 .../briar/messaging/MessageFactoryImpl.java   |  47 +++++---
 .../net/sf/briar/messaging/MessageImpl.java   |  11 +-
 .../net/sf/briar/messaging/MessageReader.java |  26 ++++-
 .../briar/messaging/MessageVerifierImpl.java  |   5 +-
 .../net/sf/briar/ProtocolIntegrationTest.java |  10 +-
 briar-tests/src/net/sf/briar/TestMessage.java |  17 ++-
 .../sf/briar/db/DatabaseComponentTest.java    |  11 +-
 .../src/net/sf/briar/db/H2DatabaseTest.java   |  50 ++++-----
 .../net/sf/briar/messaging/ConstantsTest.java |   9 +-
 .../SimplexMessagingIntegrationTest.java      |   4 +-
 24 files changed, 239 insertions(+), 149 deletions(-)
 create mode 100644 briar-android/res/drawable-hdpi/content_attachment.png
 create mode 100644 briar-android/res/drawable-mdpi/content_attachment.png
 create mode 100644 briar-android/res/drawable-xhdpi/content_attachment.png

diff --git a/briar-android/res/drawable-hdpi/content_attachment.png b/briar-android/res/drawable-hdpi/content_attachment.png
new file mode 100644
index 0000000000000000000000000000000000000000..53b963d54106ff032f364ac815f513ab91e9bd23
GIT binary patch
literal 1502
zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O`
z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y
zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP
zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg
zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN
z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83
zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw
zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg
z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVip(#+J-%-Ge`$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19yq1O$kUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnrq&
zG!Lpb1-DyVaO%|uIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zZklm1hJk@8z|+Ms
zq~g|_nfJYe6D5wDn@33qp7c5r8OY`-?&=$|=Y^KOSZs>;-6y`{u{Y!|_traf1if*~
z-=(q3bFWNC*Oac8IUAI;lcwaYo4b#{hJQ~_aarHHErvC6KMc0*eZJ@X_u9I-8#kmz
zE)3hYEuecgR{*1y1M3O~(FIH)4M5J>%^6?WPo`}8wOBeqAcjrdQMt~c<)GaShW7`y
z{dHBFyi)hThB5;-wga42a+wbvo~q+4klw@ezSgxtjM-mRVF3@<1LhxRzV~j}T(XbZ
zhRy!qrg+BhywA-Kn*6xZA~HQ#p8f6v#vdGBaZHaM@crN}N@#jDSE^yj^~!*i#`ja#
zTe{uaqT|t={y=C4pV(fO80O_KuSknOKPuN4!ceMsUHssrW`>QS8@Q_uu-Y^2ir8}h
zVAttooNg<RnA~2ydFu46E7usUCmn76x~5S={6FLV*t}bZr)inbpY%@5;kf3mn(HUG
zOf%j8hvUb=Jv~<>N^;%arM>QP(Cyw>;cv(lUB~jK;G<-L;2P^S8|O~romcRH#rCSU
z&K+%=HKp5%{23ql%oD#~DmWqex<{eY_kYVdLMja64|u!X33izHINEQU{2k`?ve%YM
zwkjqEv453F-0xMmzVQE_M757LJ5DB=oyz<nR%3e2<N6H7&1=N96`n7y;WcLZ-Ci}r
zcuw`fM=zO`MU;2`KdX3VT4B*wi}+`m;(dQivS#pXc>X|9B2BwnI#)kqGy5&404Aq7
z%#(dqMnBN9IR3gx?4jv}Ifs}1=SM^$Bt{EzSpNtoFf6ujpE0+)JOWhWdb;|#taD0e
F0st!fBvt?b

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-mdpi/content_attachment.png b/briar-android/res/drawable-mdpi/content_attachment.png
new file mode 100644
index 0000000000000000000000000000000000000000..38ff9f853ce781299b92b471da4cb9d587100a66
GIT binary patch
literal 1318
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O`
z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y
zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP
zs8ErclUHn2VXFi-*9yo63F|8<fR&VF+bTgE72zA8;GAESs$i;Tpqp%9W~g9hqGxDg
zU}<8hqhMrUXrOOsq;FuZYiM9)YHnp<r~m~@K--E^(yW49+@N*=dA3R!B_#z``ugSN
z<$C4Ddih1^`i7R4mih)p`bI{&Koz>hm3bwJ6}oxF$}kgLQj3#|G7CyF^YauyCMG83
zmzLNn0bL65LT&-v*t}wBFaZNhzap_f-%!s0<RzFwUtj!6b93RUi%Wu15$?rmaB)aw
zL8^XGYH@yPQ8F;%(v(3~6<9eJr6!i-7lq{K=fFZSAS1sdzc?emK*2fKRL@YsH!(Rg
z4<rKC;p=PVnO9trn3tUD>0+w{G(#^lGsVip(#+J-%*4pl$-u?X(ACh=#L&{!#L3yw
z&D7A`#K{n**Cju>G&eP`1g19ytk==R#nRCXr(RHE$SnZc?2=lPS(cjOR+OKs0QR(1
zCT_PF;4}}aHwCL(!2U4AsaGH97=2LGB1JV!2$+6AOnAZta^OinH4m8Hi+~B6BSroX
z0|Vm>PZ!6Kid%1{oz->;6glv`cfI3g1y{|V5huEi>P*=>RVlm7>)Ido8@D4*AG_qX
zM#n{Py_(6U0GFR$X+2Iom37<Ct$tl9o!>L7wfa?faen^0-*=18@3cMsd5?#svXo|V
z1fzBW>l%lp|CJ|ty!d?f0{_~8SD`N*`1%sh$Dc2~`<|(BdcU*&m4A&5^0lA#<tDSK
zNF_?$N?_-E7^-qlQQ!KP=9SN)0lJT^Bw92pnv^Fy*!DQE>l~~~xab$D;p2AYv~j`G
zvn$V@IcQ*Wpfq5C_oX^6=|<M}1fEq+Tt^b*pKaZ#qmsyQ%a@5)V0zL=rrm+{yBbA*
zaNLZQJX{mgXP9r*WU{BDc0<?VAntUf2Q5=FE*-O7byI=$xmqK)#l)=8qdzo0TWWe-
zmE<-m>w3fBU$%bJCa!X|#x-;HzWVPOF~w3ed(y%@+gioNZhapmw>{VtAiYXQ=P=8e
zLwa|za%X6LV*1t69ALgsZ1$vofh+$-a9-cDYPRGKeK!TAx09xA%lUb9=5NUW*56?t
n6n}^2u&!wk{qdbCo{5Jcry!0&X=_|DsO0i=^>bP0l+XkK9d6U<

literal 0
HcmV?d00001

diff --git a/briar-android/res/drawable-xhdpi/content_attachment.png b/briar-android/res/drawable-xhdpi/content_attachment.png
new file mode 100644
index 0000000000000000000000000000000000000000..1cffb3fbf2f1149821435933ea6a236ff2663ed5
GIT binary patch
literal 1717
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+*
zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn
zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%uvD1M9<K~
zz|zE0N5ROz&_LhNNZ-I**U-Sq)ZEIzPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj
z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5
zFD<cE0=g99h1>$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;Np<V
zf>iyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr4|esh**NZ(?$0
z9!LbN!`Ii!Gq1QLF)umQ)5TT^Xog;9W{Q=GrJ1RvnX#j(lYxt&p{t>#iJ_&diIcOV
zo2j9>iIX8ruS<S%X>Mv>2~2MaLa!4}y`aR9TL84#CABECEH%ZgC_h&L>}jh^+-|YJ
zX&zK>3U0Sp;?%1Tbc{YIYLTKECIn1BASOKF0y*%cpPC0u??u3b%`?qzFEGETd%8G=
zRNQ(q!#i6zP@>H^xo2ya$Hgk=Jy*E07IFu(WM5gky*0t{&W<as+=Y+N7Y1&WKG@1F
z-c_-1m%)~b+dBfKrv^D*c8gA)EP0>*UZdmeGnSc;C103-xWMLD^vvdS>^Vt&p2KH~
z_SuLoU<zsA3SiW7U|qq0g>&Nh=iX!MnSVRV-<Y;(>yHQ472I(RpYAhv9erdUe=5pH
zq1%q}Tf<b9-0->IEgABf%hj&=tlRuRYyLiVCckOR{%|JOS06MD@c0!J#Bw63{eY$c
ze_b<Q0OR}vJg!TW<~oYMU^I1m!TO;$^Wvn2#}8Ci6g7!H5UgPG5}CAYb@M}g{#@RO
zv;Xaz<hL>HIw;w;+kdt4gTk)vU-}<J{;*7!n7jLcqurJZAr1bMr4Py+ziS$?r{!+o
zvxH^7rcTc)*pAKWkxW*XSi%x{+9LRx$APGVhDv>-eGR|azCKZ6t;ps39Msg{;nbM9
zt69@9`4+3ofv{V`k6oK<-Y@^CwOFlS4&UX3Y1txXH?A;n{*`f#IOZeabK`vHqFEnT
z1wM>y648+CiFR#D6U@BC_B<kH(l_C|I#Vpg7D@Mg=d67bReJCLl(TLbT4J9fn|Xt;
zC@sD}r)1BMxoccQ@BL@LZD!%kX&W=)O0&7I8jnSt{!vkhWvShFp9RGf$?rax`Ht~b
zjm7z!QoXDpJF0ITXFd7yh2!KyVw+ycJ^LTzyw2j5>a!g$q^yrwyq<V9QsKdLtv@>K
z3zRv9_p>ZsU_4DMlHG-W<z(vvg3Fpp1#8ZKEARQ;SbXNKo4Ssn-I)%_2m9{0%C7hv
zp~L^=oYV27+20vwA1K{0Z~p$K*$+w&`R1hG|0~b5Ct~(z4TpE1H=q2?Fj-vb)59X2
z)Mq=-$~<+s%&oI!+OcM*)%q?^qPHKY+-BAP^??BE6j{0Y*F7grhSeYW(tE*KudHMF
z>J=+;(z;7j6;^$jS;l?B;Jx~dS8O+5)!gfN@8PiFMsxVJqCHQ3+g@vk%d6S;L9?Y#
sX~C`)@)xf^!j&K)Nwwi=$zQ<)2K8#|<Q1=rB|sI4r>mdKI;Vst0E}vobpQYW

literal 0
HcmV?d00001

diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
index 67c6c629b7..31fdd1dcc8 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationAdapter.java
@@ -45,6 +45,13 @@ implements OnItemClickListener {
 		else star.setImageResource(R.drawable.rating_not_important);
 		layout.addView(star);
 
+		if(!item.getContentType().equals("text/plain")) {
+			ImageView attachment = new ImageView(ctx);
+			attachment.setPadding(0, 5, 5, 5);
+			attachment.setImageResource(R.drawable.content_attachment);
+			layout.addView(attachment);
+		}
+
 		TextView subject = new TextView(ctx);
 		// Give me all the unused width
 		subject.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT,
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
index f09c7dabed..b54a41827e 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -112,21 +112,21 @@ implements OnClickListener, DatabaseListener {
 						ContactId contactId = db.addContact("Carol");
 						// Insert some fake messages to and from the contact
 						Message m = messageFactory.createPrivateMessage(null,
-								"First message's subject is short",
-								"First message's body".getBytes("UTF-8"));
+								"text/plain",
+								"First message is short".getBytes("UTF-8"));
 						db.addLocalPrivateMessage(m, contactId);
 						db.setReadFlag(m.getId(), true);
 						db.setStarredFlag(m.getId(), true);
 						Thread.sleep(1000);
 						m = messageFactory.createPrivateMessage(m.getId(),
-								"Second message's subject is also short",
-								"Second message's body".getBytes("UTF-8"));
+								"image/jpeg", new byte[1000]);
 						db.receiveMessage(contactId, m);
 						Thread.sleep(1000);
 						m = messageFactory.createPrivateMessage(m.getId(),
-								"Third message's subject is quite long to test"
-								+ " line wrapping and exciting stuff like that",
-								"Third message's body".getBytes("UTF-8"));
+								"text/plain",
+								("Third message is quite long to test line"
+								+ " wrapping and subject line extraction and"
+								+ " all that fun stuff").getBytes("UTF-8"));
 						db.addLocalPrivateMessage(m, contactId);
 						db.setReadFlag(m.getId(), true);
 					}
diff --git a/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java b/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java
index af8add02b3..543f34a939 100644
--- a/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java
+++ b/briar-api/src/net/sf/briar/api/db/GroupMessageHeader.java
@@ -9,10 +9,10 @@ public class GroupMessageHeader extends MessageHeader {
 	private final GroupId groupId;
 	private final Author author;
 
-	public GroupMessageHeader(MessageId id, MessageId parent, String subject,
-			long timestamp, boolean read, boolean starred, GroupId groupId,
-			Author author) {
-		super(id, parent, subject, timestamp, read, starred);
+	public GroupMessageHeader(MessageId id, MessageId parent,
+			String contentType, String subject, long timestamp, boolean read,
+			boolean starred, GroupId groupId, Author author) {
+		super(id, parent, contentType, subject, timestamp, read, starred);
 		this.groupId = groupId;
 		this.author = author;
 	}
diff --git a/briar-api/src/net/sf/briar/api/db/MessageHeader.java b/briar-api/src/net/sf/briar/api/db/MessageHeader.java
index b1f11d1bd5..2cbab3f51c 100644
--- a/briar-api/src/net/sf/briar/api/db/MessageHeader.java
+++ b/briar-api/src/net/sf/briar/api/db/MessageHeader.java
@@ -5,14 +5,15 @@ import net.sf.briar.api.messaging.MessageId;
 public abstract class MessageHeader {
 
 	private final MessageId id, parent;
-	private final String subject;
+	private final String contentType, subject;
 	private final long timestamp;
 	private final boolean read, starred;
 
-	protected MessageHeader(MessageId id, MessageId parent, String subject,
-			long timestamp, boolean read, boolean starred) {
+	protected MessageHeader(MessageId id, MessageId parent, String contentType,
+			String subject, long timestamp, boolean read, boolean starred) {
 		this.id = id;
 		this.parent = parent;
+		this.contentType = contentType;
 		this.subject = subject;
 		this.timestamp = timestamp;
 		this.read = read;
@@ -32,6 +33,11 @@ public abstract class MessageHeader {
 		return parent;
 	}
 
+	/** Returns the message's content type. */
+	public String getContentType() {
+		return contentType;
+	}
+
 	/** Returns the message's subject line. */
 	public String getSubject() {
 		return subject;
diff --git a/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java b/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java
index 900c2cb2a9..af55211c66 100644
--- a/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java
+++ b/briar-api/src/net/sf/briar/api/db/PrivateMessageHeader.java
@@ -8,10 +8,10 @@ public class PrivateMessageHeader extends MessageHeader {
 	private final ContactId contactId;
 	private final boolean incoming;
 
-	public PrivateMessageHeader(MessageId id, MessageId parent, String subject,
-			long timestamp, boolean read, boolean starred, ContactId contactId,
-			boolean incoming) {
-		super(id, parent, subject, timestamp, read, starred);
+	public PrivateMessageHeader(MessageId id, MessageId parent,
+			String contentType, String subject, long timestamp, boolean read,
+			boolean starred, ContactId contactId, boolean incoming) {
+		super(id, parent, contentType, subject, timestamp, read, starred);
 		this.contactId = contactId;
 		this.incoming = incoming;
 	}
diff --git a/briar-api/src/net/sf/briar/api/messaging/Message.java b/briar-api/src/net/sf/briar/api/messaging/Message.java
index f2aac57fba..b48d54cc06 100644
--- a/briar-api/src/net/sf/briar/api/messaging/Message.java
+++ b/briar-api/src/net/sf/briar/api/messaging/Message.java
@@ -23,7 +23,14 @@ public interface Message {
 	 */
 	Author getAuthor();
 
-	/** Returns the message's subject line. */
+	/** Returns the message's content type. */
+	String getContentType();
+
+	/**
+	 * Returns the message's subject line, which is created from the first 50
+	 * bytes of the message body if the content type is text/plain, or is the
+	 * empty string otherwise.
+	 */
 	String getSubject();
 
 	/** Returns the timestamp created by the message's {@link Author}. */
diff --git a/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java b/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java
index 25dee5e10c..ade4acda4e 100644
--- a/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java
+++ b/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java
@@ -7,27 +7,27 @@ import java.security.PrivateKey;
 public interface MessageFactory {
 
 	/** Creates a private message. */
-	Message createPrivateMessage(MessageId parent, String subject, byte[] body)
-			throws IOException, GeneralSecurityException;
+	Message createPrivateMessage(MessageId parent, String contentType,
+			byte[] body) throws IOException, GeneralSecurityException;
 
 	/** Creates an anonymous message to an unrestricted group. */
 	Message createAnonymousGroupMessage(MessageId parent, Group group,
-			String subject, byte[] body) throws IOException,
+			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException;
 
 	/** Creates an anonymous message to a restricted group. */
 	Message createAnonymousGroupMessage(MessageId parent, Group group,
-			PrivateKey groupKey, String subject, byte[] body)
+			PrivateKey groupKey, String contentType, byte[] body)
 					throws IOException, GeneralSecurityException;
 
 	/** Creates a pseudonymous message to an unrestricted group. */
 	Message createPseudonymousMessage(MessageId parent, Group group,
-			Author author, PrivateKey authorKey, String subject, byte[] body)
-					throws IOException, GeneralSecurityException;
+			Author author, PrivateKey authorKey, String contentType,
+			byte[] body) throws IOException, GeneralSecurityException;
 
 	/** Creates a pseudonymous message to a restricted group. */
 	Message createPseudonymousMessage(MessageId parent, Group group,
 			PrivateKey groupKey, Author author, PrivateKey authorKey,
-			String subject, byte[] body) throws IOException,
+			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException;
 }
diff --git a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
index 5f3f93ac28..4ddcedf2c5 100644
--- a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
+++ b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
@@ -36,6 +36,9 @@ public interface MessagingConstants {
 	 */
 	int MAX_BODY_LENGTH = MAX_PACKET_LENGTH - 1024;
 
+	/** The maximum length of a message's content type in UTF-8 bytes. */
+	int MAX_CONTENT_TYPE_LENGTH = 50;
+
 	/** The maximum length of a message's subject line in UTF-8 bytes. */
 	int MAX_SUBJECT_LENGTH = 100;
 
diff --git a/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java b/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java
index cf1fcf2ce4..83c81e5555 100644
--- a/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java
+++ b/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java
@@ -6,18 +6,19 @@ public class UnverifiedMessage {
 	private final MessageId parent;
 	private final Group group;
 	private final Author author;
-	private final String subject;
+	private final String contentType, subject;
 	private final long timestamp;
 	private final byte[] raw, authorSig, groupSig;
 	private final int bodyStart, bodyLength, signedByAuthor, signedByGroup;
 
 	public UnverifiedMessage(MessageId parent, Group group, Author author,
-			String subject, long timestamp, byte[] raw, byte[] authorSig,
-			byte[] groupSig, int bodyStart, int bodyLength, int signedByAuthor,
-			int signedByGroup) {
+			String contentType, String subject, long timestamp, byte[] raw,
+			byte[] authorSig, byte[] groupSig, int bodyStart, int bodyLength,
+			int signedByAuthor, int signedByGroup) {
 		this.parent = parent;
 		this.group = group;
 		this.author = author;
+		this.contentType = contentType;
 		this.subject = subject;
 		this.timestamp = timestamp;
 		this.raw = raw;
@@ -53,7 +54,16 @@ public class UnverifiedMessage {
 		return author;
 	}
 
-	/** Returns the message's subject line. */
+	/** Returns the message's content type. */
+	public String getContentType() {
+		return contentType;
+	}
+
+	/**
+	 * Returns the message's subject line, which is created from the first 50
+	 * bytes of the message body if the content type is text/plain, or is the
+	 * empty string otherwise.
+	 */
 	public String getSubject() {
 		return subject;
 	}
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 299f52bd4e..888acb4642 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -472,7 +472,7 @@ DatabaseCleaner.Callback {
 		if(m.getGroup() != null) throw new IllegalArgumentException();
 		if(m.getAuthor() != null) throw new IllegalArgumentException();
 		if(!db.addPrivateMessage(txn, m, c)) return false;
-		db.addStatus(txn, c, m.getId(), !incoming);
+		db.addStatus(txn, c, m.getId(), incoming);
 		// Count the bytes stored
 		synchronized(spaceLock) {
 			bytesStoredSinceLastCheck += m.getSerialised().length;
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 45ec3fd7f3..4836072a9e 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -125,6 +125,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " authorId HASH," // Null for private/anon messages
 					+ " authorName VARCHAR," // Null for private/anon messages
 					+ " authorKey VARCHAR," // Null for private/anon messages
+					+ " contentType VARCHAR NOT NULL,"
 					+ " subject VARCHAR NOT NULL,"
 					+ " timestamp BIGINT NOT NULL,"
 					+ " length INT NOT NULL,"
@@ -633,10 +634,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		try {
 			String sql = "INSERT INTO messages (messageId, parentId, groupId,"
-					+ " authorId, authorName, authorKey, subject, timestamp,"
-					+ " length, bodyStart, bodyLength, raw, sendability, read,"
-					+ " starred)"
-					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ZERO(),"
+					+ " authorId, authorName, authorKey, contentType, subject,"
+					+ " timestamp, length, bodyStart, bodyLength, raw,"
+					+ " sendability, read, starred)"
+					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ZERO(),"
 					+ " FALSE, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getId().getBytes());
@@ -653,13 +654,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 				ps.setString(5, a.getName());
 				ps.setBytes(6, a.getPublicKey());
 			}
-			ps.setString(7, m.getSubject());
-			ps.setLong(8, m.getTimestamp());
+			ps.setString(7, m.getContentType());
+			ps.setString(8, m.getSubject());
+			ps.setLong(9, m.getTimestamp());
 			byte[] raw = m.getSerialised();
-			ps.setInt(9, raw.length);
-			ps.setInt(10, m.getBodyStart());
-			ps.setInt(11, m.getBodyLength());
-			ps.setBytes(12, raw);
+			ps.setInt(10, raw.length);
+			ps.setInt(11, m.getBodyStart());
+			ps.setInt(12, m.getBodyLength());
+			ps.setBytes(13, raw);
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -707,22 +709,23 @@ abstract class JdbcDatabase implements Database<Connection> {
 		if(containsMessage(txn, m.getId())) return false;
 		PreparedStatement ps = null;
 		try {
-			String sql = "INSERT INTO messages (messageId, parentId, subject,"
-					+ " timestamp, length, bodyStart, bodyLength, raw,"
-					+ " contactId, read, starred)"
-					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, FALSE)";
+			String sql = "INSERT INTO messages (messageId, parentId,"
+					+ " contentType, subject, timestamp, length, bodyStart,"
+					+ " bodyLength, raw, contactId, read, starred)"
+					+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getId().getBytes());
 			if(m.getParent() == null) ps.setNull(2, BINARY);
 			else ps.setBytes(2, m.getParent().getBytes());
-			ps.setString(3, m.getSubject());
-			ps.setLong(4, m.getTimestamp());
+			ps.setString(3, m.getContentType());
+			ps.setString(4, m.getSubject());
+			ps.setLong(5, m.getTimestamp());
 			byte[] raw = m.getSerialised();
-			ps.setInt(5, raw.length);
-			ps.setInt(6, m.getBodyStart());
-			ps.setInt(7, m.getBodyLength());
-			ps.setBytes(8, raw);
-			ps.setInt(9, c.getInt());
+			ps.setInt(6, raw.length);
+			ps.setInt(7, m.getBodyStart());
+			ps.setInt(8, m.getBodyLength());
+			ps.setBytes(9, raw);
+			ps.setInt(10, c.getInt());
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -1226,7 +1229,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT messageId, parentId, authorId, authorName,"
-					+ " authorKey, subject, timestamp, read, starred"
+					+ " authorKey, contentType, subject, timestamp, read,"
+					+ " starred"
 					+ " FROM messages"
 					+ " WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
@@ -1246,12 +1250,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 					byte[] authorKey = rs.getBytes(5);
 					author = new Author(authorId, authorName, authorKey);
 				}
-				String subject = rs.getString(6);
-				long timestamp = rs.getLong(7);
-				boolean read = rs.getBoolean(8);
-				boolean starred = rs.getBoolean(9);
-				headers.add(new GroupMessageHeader(id, parent, subject,
-						timestamp, read, starred, g, author));
+				String contentType = rs.getString(6);
+				String subject = rs.getString(7);
+				long timestamp = rs.getLong(8);
+				boolean read = rs.getBoolean(9);
+				boolean starred = rs.getBoolean(10);
+				headers.add(new GroupMessageHeader(id, parent, contentType,
+						subject, timestamp, read, starred, g, author));
 			}
 			rs.close();
 			ps.close();
@@ -1268,8 +1273,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT m.messageId, parentId, subject, timestamp,"
-					+ " m.contactId, read, starred, seen"
+			String sql = "SELECT m.messageId, parentId, contentType, subject,"
+					+ " timestamp, m.contactId, read, starred, seen"
 					+ " FROM messages AS m"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
@@ -1283,14 +1288,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 				MessageId id = new MessageId(rs.getBytes(1));
 				byte[] b = rs.getBytes(2);
 				MessageId parent = b == null ? null : new MessageId(b);
-				String subject = rs.getString(3);
-				long timestamp = rs.getLong(4);
-				ContactId contactId = new ContactId(rs.getInt(5));
-				boolean read = rs.getBoolean(6);
-				boolean starred = rs.getBoolean(7);
-				boolean seen = rs.getBoolean(8);
-				headers.add(new PrivateMessageHeader(id, parent, subject,
-						timestamp, read, starred, contactId, !seen));
+				String contentType = rs.getString(3);
+				String subject = rs.getString(4);
+				long timestamp = rs.getLong(5);
+				ContactId contactId = new ContactId(rs.getInt(6));
+				boolean read = rs.getBoolean(7);
+				boolean starred = rs.getBoolean(8);
+				boolean seen = rs.getBoolean(9);
+				headers.add(new PrivateMessageHeader(id, parent, contentType,
+						subject, timestamp, read, starred, contactId, seen));
 			}
 			rs.close();
 			ps.close();
@@ -1307,8 +1313,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT m.messageId, parentId, subject, timestamp,"
-					+ " read, starred, seen"
+			String sql = "SELECT m.messageId, parentId, contentType, subject,"
+					+ " timestamp, read, starred, seen"
 					+ " FROM messages AS m"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
@@ -1323,13 +1329,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 				MessageId id = new MessageId(rs.getBytes(1));
 				byte[] b = rs.getBytes(2);
 				MessageId parent = b == null ? null : new MessageId(b);
-				String subject = rs.getString(3);
-				long timestamp = rs.getLong(4);
-				boolean read = rs.getBoolean(5);
-				boolean starred = rs.getBoolean(6);
-				boolean seen = rs.getBoolean(7);
-				headers.add(new PrivateMessageHeader(id, parent, subject,
-						timestamp, read, starred, c, !seen));
+				String contentType = rs.getString(3);
+				String subject = rs.getString(4);
+				long timestamp = rs.getLong(5);
+				boolean read = rs.getBoolean(6);
+				boolean starred = rs.getBoolean(7);
+				boolean seen = rs.getBoolean(8);
+				headers.add(new PrivateMessageHeader(id, parent, contentType,
+						subject, timestamp, read, starred, c, seen));
 			}
 			rs.close();
 			ps.close();
diff --git a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
index 43d5f0f4b9..bf329c80c4 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
@@ -1,6 +1,7 @@
 package net.sf.briar.messaging;
 
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
@@ -11,6 +12,9 @@ import static net.sf.briar.api.messaging.Types.MESSAGE;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
 import java.security.GeneralSecurityException;
 import java.security.PrivateKey;
 import java.security.SecureRandom;
@@ -40,6 +44,7 @@ class MessageFactoryImpl implements MessageFactory {
 	private final MessageDigest messageDigest;
 	private final WriterFactory writerFactory;
 	private final Clock clock;
+	private final CharsetDecoder decoder;
 
 	@Inject
 	MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory,
@@ -50,44 +55,46 @@ class MessageFactoryImpl implements MessageFactory {
 		messageDigest = crypto.getMessageDigest();
 		this.writerFactory = writerFactory;
 		this.clock = clock;
+		decoder = Charset.forName("UTF-8").newDecoder();
 	}
 
-	public Message createPrivateMessage(MessageId parent, String subject,
+	public Message createPrivateMessage(MessageId parent, String contentType,
 			byte[] body) throws IOException, GeneralSecurityException {
-		return createMessage(parent, null, null, null, null, subject, body);
+		return createMessage(parent, null, null, null, null, contentType, body);
 	}
 
 	public Message createAnonymousGroupMessage(MessageId parent, Group group,
-			String subject, byte[] body) throws IOException,
+			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException {
-		return createMessage(parent, group, null, null, null, subject, body);
+		return createMessage(parent, group, null, null, null, contentType,
+				body);
 	}
 
 	public Message createAnonymousGroupMessage(MessageId parent, Group group,
-			PrivateKey groupKey, String subject, byte[] body)
+			PrivateKey groupKey, String contentType, byte[] body)
 					throws IOException, GeneralSecurityException {
-		return createMessage(parent, group, groupKey, null, null, subject,
+		return createMessage(parent, group, groupKey, null, null, contentType,
 				body);
 	}
 
 	public Message createPseudonymousMessage(MessageId parent, Group group,
-			Author author, PrivateKey authorKey, String subject, byte[] body)
+			Author author, PrivateKey authorKey, String contentType, byte[] body)
 					throws IOException, GeneralSecurityException {
-		return createMessage(parent, group, null, author, authorKey, subject,
-				body);
+		return createMessage(parent, group, null, author, authorKey,
+				contentType, body);
 	}
 
 	public Message createPseudonymousMessage(MessageId parent, Group group,
 			PrivateKey groupKey, Author author, PrivateKey authorKey,
-			String subject, byte[] body) throws IOException,
+			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException {
 		return createMessage(parent, group, groupKey, author, authorKey,
-				subject, body);
+				contentType, body);
 	}
 
 	private Message createMessage(MessageId parent, Group group,
 			PrivateKey groupKey, Author author, PrivateKey authorKey,
-			String subject, byte[] body) throws IOException,
+			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException {
 		// Validate the arguments
 		if((author == null) != (authorKey == null))
@@ -95,7 +102,7 @@ class MessageFactoryImpl implements MessageFactory {
 		if((group == null || group.getPublicKey() == null)
 				!= (groupKey == null))
 			throw new IllegalArgumentException();
-		if(subject.getBytes("UTF-8").length > MAX_SUBJECT_LENGTH)
+		if(contentType.getBytes("UTF-8").length > MAX_CONTENT_TYPE_LENGTH)
 			throw new IllegalArgumentException();
 		if(body.length > MAX_BODY_LENGTH)
 			throw new IllegalArgumentException();
@@ -127,7 +134,7 @@ class MessageFactoryImpl implements MessageFactory {
 		else writeGroup(w, group);
 		if(author == null) w.writeNull();
 		else writeAuthor(w, author);
-		w.writeString(subject);
+		w.writeString(contentType);
 		long timestamp = clock.currentTimeMillis();
 		w.writeInt64(timestamp);
 		byte[] salt = new byte[SALT_LENGTH];
@@ -158,7 +165,17 @@ class MessageFactoryImpl implements MessageFactory {
 		// Hash the message, including the signatures, to get the message ID
 		w.removeConsumer(digestingConsumer);
 		MessageId id = new MessageId(messageDigest.digest());
-		return new MessageImpl(id, parent, group, author, subject,
+		// If the content type is text/plain, extract a subject line
+		String subject;
+		if(contentType.equals("text/plain")) {
+			byte[] start = new byte[Math.min(MAX_SUBJECT_LENGTH, body.length)];
+			System.arraycopy(body, 0, start, 0, start.length);
+			decoder.reset();
+			subject = decoder.decode(ByteBuffer.wrap(start)).toString();
+		} else {
+			subject = "";
+		}
+		return new MessageImpl(id, parent, group, author, contentType, subject,
 				timestamp, out.toByteArray(), bodyStart, body.length);
 	}
 
diff --git a/briar-core/src/net/sf/briar/messaging/MessageImpl.java b/briar-core/src/net/sf/briar/messaging/MessageImpl.java
index 3472613223..4ff50f3102 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageImpl.java
@@ -12,14 +12,14 @@ class MessageImpl implements Message {
 	private final MessageId id, parent;
 	private final Group group;
 	private final Author author;
-	private final String subject;
+	private final String contentType, subject;
 	private final long timestamp;
 	private final byte[] raw;
 	private final int bodyStart, bodyLength;
 
 	public MessageImpl(MessageId id, MessageId parent, Group group,
-			Author author, String subject, long timestamp, byte[] raw,
-			int bodyStart, int bodyLength) {
+			Author author, String contentType, String subject, long timestamp,
+			byte[] raw, int bodyStart, int bodyLength) {
 		if(bodyStart + bodyLength > raw.length)
 			throw new IllegalArgumentException();
 		if(bodyLength > MAX_BODY_LENGTH)
@@ -28,6 +28,7 @@ class MessageImpl implements Message {
 		this.parent = parent;
 		this.group = group;
 		this.author = author;
+		this.contentType = contentType;
 		this.subject = subject;
 		this.timestamp = timestamp;
 		this.raw = raw;
@@ -51,6 +52,10 @@ class MessageImpl implements Message {
 		return author;
 	}
 
+	public String getContentType() {
+		return contentType;
+	}
+
 	public String getSubject() {
 		return subject;
 	}
diff --git a/briar-core/src/net/sf/briar/messaging/MessageReader.java b/briar-core/src/net/sf/briar/messaging/MessageReader.java
index 0345b70c6e..de0bf74ac5 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageReader.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageReader.java
@@ -1,6 +1,7 @@
 package net.sf.briar.messaging;
 
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
@@ -8,6 +9,9 @@ import static net.sf.briar.api.messaging.MessagingConstants.SALT_LENGTH;
 import static net.sf.briar.api.messaging.Types.MESSAGE;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
 
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.messaging.Author;
@@ -24,11 +28,13 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 
 	private final StructReader<Group> groupReader;
 	private final StructReader<Author> authorReader;
+	private final CharsetDecoder decoder;
 
 	MessageReader(StructReader<Group> groupReader,
 			StructReader<Author> authorReader) {
 		this.groupReader = groupReader;
 		this.authorReader = authorReader;
+		decoder = Charset.forName("UTF-8").newDecoder();
 	}
 
 	public UnverifiedMessage readStruct(Reader r) throws IOException {
@@ -55,8 +61,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 		Author author = null;
 		if(r.hasNull()) r.readNull();
 		else author = authorReader.readStruct(r);
-		// Read the subject
-		String subject = r.readString(MAX_SUBJECT_LENGTH);
+		// Read the content type
+		String contentType = r.readString(MAX_CONTENT_TYPE_LENGTH);
 		// Read the timestamp
 		long timestamp = r.readInt64();
 		if(timestamp < 0) throw new FormatException();
@@ -65,6 +71,16 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 		if(salt.length < SALT_LENGTH) throw new FormatException();
 		// Read the message body
 		byte[] body = r.readBytes(MAX_BODY_LENGTH);
+		// If the content type is text/plain, extract a subject line
+		String subject;
+		if(contentType.equals("text/plain")) {
+			byte[] start = new byte[Math.min(MAX_SUBJECT_LENGTH, body.length)];
+			System.arraycopy(body, 0, start, 0, start.length);
+			decoder.reset();
+			subject = decoder.decode(ByteBuffer.wrap(start)).toString();
+		} else {
+			subject = "";
+		}
 		// Record the offset of the body within the message
 		int bodyStart = (int) counting.getCount() - body.length;
 		// Record the length of the data covered by the author's signature
@@ -83,8 +99,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 		r.removeConsumer(counting);
 		r.removeConsumer(copying);
 		byte[] raw = copying.getCopy();
-		return new UnverifiedMessage(parent, group, author, subject, timestamp,
-				raw, authorSig, groupSig, bodyStart, body.length,
-				signedByAuthor, signedByGroup);
+		return new UnverifiedMessage(parent, group, author, contentType,
+				subject, timestamp, raw, authorSig, groupSig, bodyStart,
+				body.length, signedByAuthor, signedByGroup);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
index e1dc4896e7..22abc26555 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
@@ -53,7 +53,8 @@ class MessageVerifierImpl implements MessageVerifier {
 			if(!signature.verify(m.getGroupSignature()))
 				throw new GeneralSecurityException();
 		}
-		return new MessageImpl(id, m.getParent(), group, author, m.getSubject(),
-				m.getTimestamp(), raw, m.getBodyStart(), m.getBodyLength());
+		return new MessageImpl(id, m.getParent(), group, author,
+				m.getContentType(), m.getSubject(), m.getTimestamp(), raw,
+				m.getBodyStart(), m.getBodyLength());
 	}
 }
diff --git a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
index 18aff79214..e838923c98 100644
--- a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
@@ -70,7 +70,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 	private final Group group, group1;
 	private final Message message, message1, message2, message3;
 	private final String authorName = "Alice";
-	private final String subject = "Hello";
+	private final String contentType = "text/plain";
 	private final String messageBody = "Hello world";
 	private final Collection<MessageId> messageIds;
 	private final TransportId transportId;
@@ -107,16 +107,16 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		// Create two messages to each group: one anonymous, one pseudonymous
 		MessageFactory messageFactory = i.getInstance(MessageFactory.class);
 		message = messageFactory.createAnonymousGroupMessage(null, group,
-				subject, messageBody.getBytes("UTF-8"));
+				contentType, messageBody.getBytes("UTF-8"));
 		message1 = messageFactory.createAnonymousGroupMessage(null, group1,
-				groupKeyPair.getPrivate(), subject,
+				groupKeyPair.getPrivate(), contentType,
 				messageBody.getBytes("UTF-8"));
 		message2 = messageFactory.createPseudonymousMessage(null, group,
-				author, authorKeyPair.getPrivate(), subject,
+				author, authorKeyPair.getPrivate(), contentType,
 				messageBody.getBytes("UTF-8"));
 		message3 = messageFactory.createPseudonymousMessage(null, group1,
 				groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
-				subject, messageBody.getBytes("UTF-8"));
+				contentType, messageBody.getBytes("UTF-8"));
 		messageIds = Arrays.asList(message.getId(), message1.getId(),
 				message2.getId(), message3.getId());
 		// Create some transport properties
diff --git a/briar-tests/src/net/sf/briar/TestMessage.java b/briar-tests/src/net/sf/briar/TestMessage.java
index 0c4ae532a9..9a8283505e 100644
--- a/briar-tests/src/net/sf/briar/TestMessage.java
+++ b/briar-tests/src/net/sf/briar/TestMessage.java
@@ -13,23 +13,26 @@ public class TestMessage implements Message {
 	private final MessageId id, parent;
 	private final Group group;
 	private final Author author;
-	private final String subject;
+	private final String contentType, subject;
 	private final long timestamp;
 	private final byte[] raw;
 	private final int bodyStart, bodyLength;
 
 	public TestMessage(MessageId id, MessageId parent, Group group,
-			Author author, String subject, long timestamp, byte[] raw) {
-		this(id, parent, group, author, subject, timestamp, raw, 0, raw.length);
+			Author author, String contentType, String subject, long timestamp,
+			byte[] raw) {
+		this(id, parent, group, author, contentType, subject, timestamp, raw, 0,
+				raw.length);
 	}
 
 	public TestMessage(MessageId id, MessageId parent, Group group,
-			Author author, String subject, long timestamp, byte[] raw,
-			int bodyStart, int bodyLength) {
+			Author author, String contentType, String subject, long timestamp,
+			byte[] raw, int bodyStart, int bodyLength) {
 		this.id = id;
 		this.parent = parent;
 		this.group = group;
 		this.author = author;
+		this.contentType = contentType;
 		this.subject = subject;
 		this.timestamp = timestamp;
 		this.raw = raw;
@@ -53,6 +56,10 @@ public class TestMessage implements Message {
 		return author;
 	}
 
+	public String getContentType() {
+		return contentType;
+	}
+
 	public String getSubject() {
 		return subject;
 	}
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 1cf9044ffa..72958c9de3 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -59,7 +59,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 	protected final AuthorId authorId;
 	protected final Author author;
 	protected final MessageId messageId, messageId1;
-	protected final String subject;
+	protected final String contentType, subject;
 	protected final long timestamp;
 	protected final int size;
 	protected final byte[] raw;
@@ -80,14 +80,15 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		author = new Author(authorId, "Alice", new byte[60]);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
+		contentType = "text/plain";
 		subject = "Foo";
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
-		message = new TestMessage(messageId, null, group, author, subject,
-				timestamp, raw);
-		privateMessage = new TestMessage(messageId, null, null, null, subject,
-				timestamp, raw);
+		message = new TestMessage(messageId, null, group, author, contentType,
+				subject, timestamp, raw);
+		privateMessage = new TestMessage(messageId, null, null, null,
+				contentType, subject, timestamp, raw);
 		transportId = new TransportId(TestUtils.getRandomId());
 		transportProperties = new TransportProperties(Collections.singletonMap(
 				"foo", "bar"));
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index ec00dd3f61..3126208083 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -56,7 +56,7 @@ public class H2DatabaseTest extends BriarTestCase {
 	private final AuthorId authorId;
 	private final Author author;
 	private final MessageId messageId, messageId1;
-	private final String subject;
+	private final String contentType, subject;
 	private final long timestamp;
 	private final int size;
 	private final byte[] raw;
@@ -73,15 +73,16 @@ public class H2DatabaseTest extends BriarTestCase {
 		author = new Author(authorId, "Alice", new byte[60]);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
+		contentType = "text/plain";
 		subject = "Foo";
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		random.nextBytes(raw);
-		message = new TestMessage(messageId, null, group, author, subject,
-				timestamp, raw);
-		privateMessage = new TestMessage(messageId1, null, null, null,
+		message = new TestMessage(messageId, null, group, author, contentType,
 				subject, timestamp, raw);
+		privateMessage = new TestMessage(messageId1, null, null, null,
+				contentType, subject, timestamp, raw);
 		transportId = new TransportId(TestUtils.getRandomId());
 		contactId = new ContactId(1);
 		contactName = "Alice";
@@ -552,7 +553,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		Author author1 = new Author(authorId1, "Bob", new byte[60]);
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		Message message1 = new TestMessage(messageId1, null, group, author1,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -584,12 +585,12 @@ public class H2DatabaseTest extends BriarTestCase {
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
 		Group group1 = new Group(groupId1, "Group name", null);
 		Message child1 = new TestMessage(childId1, messageId, group, author,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		Message child2 = new TestMessage(childId2, messageId, group, author,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		// The third child is in a different group
 		Message child3 = new TestMessage(childId3, messageId, group1, author,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -620,7 +621,7 @@ public class H2DatabaseTest extends BriarTestCase {
 	public void testGetOldMessages() throws Exception {
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		Message message1 = new TestMessage(messageId1, null, group, author,
-				subject, timestamp + 1000, raw);
+				contentType, subject, timestamp + 1000, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -651,7 +652,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		byte[] largeBody = new byte[ONE_MEGABYTE];
 		for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i;
 		Message message1 = new TestMessage(messageId, null, group, author,
-				subject, timestamp, largeBody);
+				contentType, subject, timestamp, largeBody);
 		Database<Connection> db = open(false);
 
 		// Sanity check: there should be enough space on disk for this test
@@ -1130,8 +1131,8 @@ public class H2DatabaseTest extends BriarTestCase {
 
 		// A message with no parent should return null
 		MessageId childId = new MessageId(TestUtils.getRandomId());
-		Message child = new TestMessage(childId, null, group, null, subject,
-				timestamp, raw);
+		Message child = new TestMessage(childId, null, group, null, contentType,
+				subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		assertTrue(db.containsMessage(txn, childId));
 		assertNull(db.getGroupMessageParent(txn, childId));
@@ -1152,7 +1153,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
 		Message child = new TestMessage(childId, parentId, group, null,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		assertTrue(db.containsMessage(txn, childId));
 		assertFalse(db.containsMessage(txn, parentId));
@@ -1178,9 +1179,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
 		Message child = new TestMessage(childId, parentId, group, null,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		Message parent = new TestMessage(parentId, null, group1, null,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		db.addGroupMessage(txn, parent);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1203,7 +1204,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// A message with a private parent should return null
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		Message child = new TestMessage(childId, messageId1, group, null,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		db.addPrivateMessage(txn, privateMessage, contactId);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1227,9 +1228,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId childId = new MessageId(TestUtils.getRandomId());
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
 		Message child = new TestMessage(childId, parentId, group, null,
-				subject, timestamp, raw);
-		Message parent = new TestMessage(parentId, null, group, null, subject,
-				timestamp, raw);
+				contentType, subject, timestamp, raw);
+		Message parent = new TestMessage(parentId, null, group, null,
+				contentType, subject, timestamp, raw);
 		db.addGroupMessage(txn, child);
 		db.addGroupMessage(txn, parent);
 		assertTrue(db.containsMessage(txn, childId));
@@ -1252,9 +1253,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Store a couple of messages
 		int bodyLength = raw.length - 20;
 		Message message1 = new TestMessage(messageId, null, group, null,
-				subject, timestamp, raw, 5, bodyLength);
+				contentType, subject, timestamp, raw, 5, bodyLength);
 		Message privateMessage1 = new TestMessage(messageId1, null, null,
-				null, subject, timestamp, raw, 10, bodyLength);
+				null, contentType, subject, timestamp, raw, 10, bodyLength);
 		db.addGroupMessage(txn, message1);
 		db.addPrivateMessage(txn, privateMessage1, contactId);
 
@@ -1294,7 +1295,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId parentId = new MessageId(TestUtils.getRandomId());
 		long timestamp1 = System.currentTimeMillis();
 		Message message1 = new TestMessage(messageId1, parentId, group, author,
-				subject, timestamp1, raw);
+				contentType, subject, timestamp1, raw);
 		db.addGroupMessage(txn, message1);
 		// Mark one of the messages read
 		assertFalse(db.setReadFlag(txn, messageId, true));
@@ -1352,6 +1353,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(m.getGroup().getId(), h.getGroupId());
 		if(m.getAuthor() == null) assertNull(h.getAuthor());
 		else assertEquals(m.getAuthor(), h.getAuthor());
+		assertEquals(m.getContentType(), h.getContentType());
 		assertEquals(m.getSubject(), h.getSubject());
 		assertEquals(m.getTimestamp(), h.getTimestamp());
 	}
@@ -1423,13 +1425,13 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addGroupMessage(txn, message);
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		Message message1 = new TestMessage(messageId1, null, group, author,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		db.addGroupMessage(txn, message1);
 
 		// Store one message in the second group
 		MessageId messageId2 = new MessageId(TestUtils.getRandomId());
 		Message message2 = new TestMessage(messageId2, null, group1, author,
-				subject, timestamp, raw);
+				contentType, subject, timestamp, raw);
 		db.addGroupMessage(txn, message2);
 
 		// Mark one of the messages in the first group read
diff --git a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
index f715b6f2c9..76e4d6ccab 100644
--- a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
@@ -8,7 +8,7 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTIES_PER_T
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PROPERTY_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PUBLIC_KEY_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SIGNATURE_LENGTH;
-import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
 
 import java.io.ByteArrayOutputStream;
@@ -123,15 +123,16 @@ public class ConstantsTest extends BriarTestCase {
 				crypto.generateSignatureKeyPair().getPrivate();
 		PrivateKey authorPrivate =
 				crypto.generateSignatureKeyPair().getPrivate();
-		String subject = createRandomString(MAX_SUBJECT_LENGTH);
+		String contentType = createRandomString(MAX_CONTENT_TYPE_LENGTH);
 		byte[] body = new byte[MAX_BODY_LENGTH];
 		Message message = messageFactory.createPseudonymousMessage(parent,
-				group, groupPrivate, author, authorPrivate, subject, body);
+				group, groupPrivate, author, authorPrivate, contentType, body);
 		// Check the size of the serialised message
 		int length = message.getSerialised().length;
 		assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH
 				+ MAX_PUBLIC_KEY_LENGTH + MAX_AUTHOR_NAME_LENGTH
-				+ MAX_PUBLIC_KEY_LENGTH + MAX_BODY_LENGTH);
+				+ MAX_PUBLIC_KEY_LENGTH + MAX_CONTENT_TYPE_LENGTH
+				+ MAX_BODY_LENGTH);
 		assertTrue(length <= MAX_PACKET_LENGTH);
 	}
 
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
index 1257c0eccd..105518b8f5 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -112,10 +112,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		db.addEndpoint(ep);
 		km.endpointAdded(ep, initialSecret.clone());
 		// Send Bob a message
-		String subject = "Hello";
+		String contentType = "text/plain";
 		byte[] body = "Hi Bob!".getBytes("UTF-8");
 		MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
-		Message message = messageFactory.createPrivateMessage(null, subject,
+		Message message = messageFactory.createPrivateMessage(null, contentType,
 				body);
 		db.addLocalPrivateMessage(message, contactId);
 		// Create an outgoing simplex connection
-- 
GitLab