diff --git a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java index 70d326ca42266b50da015e30191740ad786e0a2c..e91088f0271f920e236e701c7fb0afa23e4935c8 100644 --- a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java @@ -128,12 +128,7 @@ public class WriteBlogPostActivity extends BriarActivity } private void enableOrDisablePublishButton() { - int bodyLength = - StringUtils.toUtf8(input.getText().toString()).length; - if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH) - input.setSendButtonEnabled(true); - else - input.setSendButtonEnabled(false); + input.setSendButtonEnabled(input.getText().length() > 0); } @Override @@ -142,6 +137,7 @@ public class WriteBlogPostActivity extends BriarActivity input.setVisibility(GONE); progressBar.setVisibility(VISIBLE); + body = StringUtils.truncateUtf8(body, MAX_BLOG_POST_BODY_LENGTH); storePost(body); } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 1d563ad0ce1679a7d218e911d083d3d270eb95d4..af2e5e0b44ef156dc0f15d7100e0eae8cf08327d 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -100,6 +100,7 @@ import static java.util.logging.Level.WARNING; import static org.briarproject.android.contact.ConversationItem.IncomingItem; import static org.briarproject.android.contact.ConversationItem.OutgoingItem; import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH; public class ConversationActivity extends BriarActivity implements EventListener, IntroductionHandler, TextInputListener { @@ -656,6 +657,7 @@ public class ConversationActivity extends BriarActivity public void onSendClick(String text) { markMessagesRead(); if (text.equals("")) return; + text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_BODY_LENGTH); long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); createMessage(StringUtils.toUtf8(text), timestamp); diff --git a/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java b/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java index 4b5b3c3cd0cbfed5282613b3e8c69005d5a1d6a4..019aff689978c3354668d9af1e38cfebc26d8768 100644 --- a/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java +++ b/briar-android/src/org/briarproject/android/introduction/IntroductionMessageFragment.java @@ -20,6 +20,7 @@ import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.db.DbException; import org.briarproject.api.introduction.IntroductionManager; +import org.briarproject.util.StringUtils; import java.util.logging.Logger; @@ -33,6 +34,7 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.WARNING; +import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; public class IntroductionMessageFragment extends BaseFragment implements TextInputView.TextInputListener { @@ -176,6 +178,7 @@ public class IntroductionMessageFragment extends BaseFragment ui.message.setSendButtonEnabled(false); String msg = ui.message.getText().toString(); + msg = StringUtils.truncateUtf8(msg, MAX_INTRODUCTION_MESSAGE_LENGTH); makeIntroduction(contact1, contact2, msg); // don't wait for the introduction to be made before finishing activity diff --git a/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java b/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java index 5a8f4747639dec6154ac2de9f03c82bc01c44f29..ea79398cf0167b62722e3fdbad7417760fa6cc38 100644 --- a/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java +++ b/briar-android/src/org/briarproject/android/sharing/ShareMessageFragment.java @@ -15,6 +15,7 @@ import org.briarproject.api.blogs.BlogSharingManager; import org.briarproject.api.contact.ContactId; import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.sync.GroupId; +import org.briarproject.util.StringUtils; import java.util.ArrayList; import java.util.Collection; @@ -24,6 +25,7 @@ import javax.inject.Inject; import static org.briarproject.android.sharing.ShareActivity.CONTACTS; import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds; import static org.briarproject.api.sharing.SharingConstants.GROUP_ID; +import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; abstract class ShareMessageFragment extends BaseFragment implements TextInputListener { @@ -103,6 +105,7 @@ abstract class ShareMessageFragment extends BaseFragment // disable button to prevent accidental double invitations ui.message.setSendButtonEnabled(false); + msg = StringUtils.truncateUtf8(msg, MAX_INVITATION_MESSAGE_LENGTH); share(msg); // don't wait for the invitation to be made before finishing activity diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java index 13e450c1c77843198469dfc8746afbbf09dd64f4..e88fb182c7f40dc4e5cf5e0f0165a589a17de7ef 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java @@ -13,6 +13,9 @@ public interface BlogConstants { /** The maximum length of a blog post's body in bytes. */ int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; + /** The maximum length of a blog comment in bytes. */ + int MAX_BLOG_COMMENT_LENGTH = MAX_BLOG_POST_BODY_LENGTH; + /** The internal name of personal blogs that are created automatically */ String PERSONAL_BLOG_NAME = "briar.PERSONAL_BLOG_NAME"; diff --git a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java index 5a75b76475527f93a22fa98bf31e2102e4c88d03..8584dff59a58243709a24eeb14cba59bbcf690b0 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java @@ -1,61 +1,7 @@ package org.briarproject.api.blogs; -import org.briarproject.api.contact.Contact; -import org.briarproject.api.contact.ContactId; -import org.briarproject.api.db.DbException; -import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sharing.SharingManager; -import org.briarproject.api.sync.ClientId; -import org.briarproject.api.sync.GroupId; - -import java.util.Collection; public interface BlogSharingManager extends SharingManager<Blog> { - /** - * Returns the unique ID of the blog sharing client. - */ - ClientId getClientId(); - - /** - * Sends an invitation to share the given blog with the given contact - * and sends an optional message along with it. - */ - void sendInvitation(GroupId groupId, ContactId contactId, - String message) throws DbException; - - /** - * Responds to a pending blog invitation - */ - void respondToInvitation(Blog b, Contact c, boolean accept) - throws DbException; - - /** - * Returns all blogs sharing messages sent by the Contact - * identified by contactId. - */ - Collection<InvitationMessage> getInvitationMessages( - ContactId contactId) throws DbException; - - /** - * Returns all blogs to which the user has been invited. - */ - Collection<InvitationItem> getInvitations() throws DbException; - - /** - * Returns all contacts who are sharing the given blog with us. - */ - Collection<Contact> getSharedBy(GroupId g) throws DbException; - - /** - * Returns the IDs of all contacts with whom the given blog is shared. - */ - Collection<Contact> getSharedWith(GroupId g) throws DbException; - - /** - * Returns true if the blog not already shared and no invitation is open - */ - boolean canBeShared(GroupId g, Contact c) throws DbException; - } diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java index a734abc7d794863f0f0e668d3881d92292dba07a..ec0135c75f71e14659082fe955e828cdbd8f2ae5 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java @@ -1,51 +1,7 @@ package org.briarproject.api.forum; -import org.briarproject.api.contact.Contact; -import org.briarproject.api.contact.ContactId; -import org.briarproject.api.db.DbException; -import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sharing.SharingManager; -import org.briarproject.api.sync.ClientId; -import org.briarproject.api.sync.GroupId; - -import java.util.Collection; public interface ForumSharingManager extends SharingManager<Forum> { - /** Returns the unique ID of the forum sharing client. */ - ClientId getClientId(); - - /** - * Sends an invitation to share the given forum with the given contact - * and sends an optional message along with it. - */ - void sendInvitation(GroupId groupId, ContactId contactId, - String message) throws DbException; - - /** - * Responds to a pending forum invitation - */ - void respondToInvitation(Forum f, Contact c, boolean accept) - throws DbException; - - /** - * Returns all forum sharing messages sent by the Contact - * identified by contactId. - */ - Collection<InvitationMessage> getInvitationMessages( - ContactId contactId) throws DbException; - - /** Returns all forums to which the user has been invited. */ - Collection<InvitationItem> getInvitations() throws DbException; - - /** Returns all contacts who are sharing the given forum with us. */ - Collection<Contact> getSharedBy(GroupId g) throws DbException; - - /** Returns the IDs of all contacts with whom the given forum is shared. */ - Collection<Contact> getSharedWith(GroupId g) throws DbException; - - /** Returns true if the forum not already shared and no invitation is open */ - boolean canBeShared(GroupId g, Contact c) throws DbException; - } diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java index de74b390ff1934878fd82ccc54e2accf886e55c6..4e0e0582836364bd721f3914746fabce09f4f836 100644 --- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java +++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java @@ -1,5 +1,7 @@ package org.briarproject.api.introduction; +import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; + public interface IntroductionConstants { /* Protocol roles */ @@ -30,8 +32,18 @@ public interface IntroductionConstants { String SIGNATURE = "signature"; /* Validation Constants */ + + /** + * The length of the message authentication code in bytes. + */ int MAC_LENGTH = 32; + /** + * The maximum length of the introducer's optional message to the + * introducees in UTF-8 bytes. + */ + int MAX_INTRODUCTION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024; + /* Introducer Local State Metadata */ String STATE = "state"; String ROLE = "role"; diff --git a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java index 47a990669d9ebd0098341e2b89207e6455bd3035..8826b524bb00a9c13e648dd1e95855b2a21ea8f4 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java @@ -1,10 +1,20 @@ package org.briarproject.api.sharing; +import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; + public interface SharingConstants { - /** The length of a sharing session's random salt in bytes. */ + /** + * The length of a sharing session's random salt in bytes. + */ int SHARING_SALT_LENGTH = 32; + /** + * The maximum length of the optional message from the inviter to the + * invitee in UTF-8 bytes. + */ + int MAX_INVITATION_MESSAGE_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1204; + String CONTACT_ID = "contactId"; String GROUP_ID = "groupId"; String TO_BE_SHARED_BY_US = "toBeSharedByUs"; diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java index c633256f1245fe0e4b98f3aa020ae8680690a1a6..b4613cd94035ef209d7d82b24c83a6126a841453 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java @@ -11,15 +11,17 @@ import java.util.Collection; public interface SharingManager<S extends Shareable> extends MessageTracker { - /** Returns the unique ID of the group sharing client. */ + /** + * Returns the unique ID of the group sharing client. + */ ClientId getClientId(); /** - * Sends an invitation to share the given shareable with the given contact + * Sends an invitation to share the given group with the given contact * and sends an optional message along with it. */ void sendInvitation(GroupId groupId, ContactId contactId, - String message) throws DbException; + String message) throws DbException; /** * Responds to a pending group invitation @@ -28,22 +30,29 @@ public interface SharingManager<S extends Shareable> extends MessageTracker { throws DbException; /** - * Returns all group sharing messages sent by the Contact - * identified by contactId. + * Returns all group sharing messages sent by the given contact. */ Collection<InvitationMessage> getInvitationMessages( ContactId contactId) throws DbException; - /** Returns all invitations to shareables. */ + /** + * Returns all invitations to groups. + */ Collection<InvitationItem> getInvitations() throws DbException; - /** Returns all contacts who are sharing the given group with us. */ + /** + * Returns all contacts who are sharing the given group with us. + */ Collection<Contact> getSharedBy(GroupId g) throws DbException; - /** Returns the IDs of all contacts with whom the given group is shared. */ + /** + * Returns all contacts with whom the given group is shared. + */ Collection<Contact> getSharedWith(GroupId g) throws DbException; - /** Returns true if the group not already shared and no invitation is open */ + /** + * Returns true if the group not already shared and no invitation is open + */ boolean canBeShared(GroupId g, Contact c) throws DbException; } diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java index 62e6bc0addaecdd2e868bd780d68205f54f08e36..fdab87cac682357641e27d1b1f92674b197b85c3 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java @@ -11,6 +11,7 @@ import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.briarproject.api.system.Clock; +import org.briarproject.util.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,6 +19,7 @@ import java.security.GeneralSecurityException; import javax.inject.Inject; +import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_COMMENT_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.MessageType.COMMENT; import static org.briarproject.api.blogs.MessageType.POST; @@ -42,7 +44,8 @@ class BlogPostFactoryImpl implements BlogPostFactory { throws FormatException, GeneralSecurityException { // Validate the arguments - if (body.length() > MAX_BLOG_POST_BODY_LENGTH) + int bodyLength = StringUtils.toUtf8(body).length; + if (bodyLength > MAX_BLOG_POST_BODY_LENGTH) throw new IllegalArgumentException(); // Serialise the data to be signed @@ -62,6 +65,13 @@ class BlogPostFactoryImpl implements BlogPostFactory { @Nullable String comment, MessageId pOriginalId, MessageId parentId) throws FormatException, GeneralSecurityException { + if (comment != null) { + int commentLength = StringUtils.toUtf8(comment).length; + if (commentLength == 0) throw new IllegalArgumentException(); + if (commentLength > MAX_BLOG_COMMENT_LENGTH) + throw new IllegalArgumentException(); + } + long timestamp = clock.currentTimeMillis(); // Generate the signature diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java index 328153ad7f764443f6843fcda35097e8e979080e..29908c6d3d8230368e9fe4abc508e2eeefe12f8e 100644 --- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java +++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java @@ -40,6 +40,7 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_READ; import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP; import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED; import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE; +import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_COMMENT_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH; import static org.briarproject.api.blogs.MessageType.COMMENT; import static org.briarproject.api.blogs.MessageType.POST; @@ -125,7 +126,7 @@ class BlogPostValidator extends BdfMessageValidator { // Comment String comment = body.getOptionalString(0); - checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH); + checkLength(comment, 1, MAX_BLOG_COMMENT_LENGTH); // parent_original_id // The ID of a post or comment in this group or another group @@ -216,7 +217,7 @@ class BlogPostValidator extends BdfMessageValidator { // Body of Wrapped Comment String comment = body.getOptionalString(2); - checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH); + checkLength(comment, 1, MAX_BLOG_COMMENT_LENGTH); // c_parent_original_id // Taken from the original comment diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java index fda189d3a77ee975ae289bb655e745b9eb744802..e3daef72e5abe558994e59a91c8a944131617031 100644 --- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java +++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java @@ -484,10 +484,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { private String getPostBody(String text) { text = clean(text, article); - byte[] textBytes = StringUtils.toUtf8(text); - if (textBytes.length <= MAX_BLOG_POST_BODY_LENGTH) - return text; - return StringUtils.fromUtf8(textBytes, 0, MAX_BLOG_POST_BODY_LENGTH); + return StringUtils.truncateUtf8(text, MAX_BLOG_POST_BODY_LENGTH); } /** diff --git a/briar-core/src/org/briarproject/introduction/IntroducerManager.java b/briar-core/src/org/briarproject/introduction/IntroducerManager.java index b8c414a4778e1c80940e7bba523a8259aa8edc47..a7ecd00cbe07ca81b03a281a7622c86dacac9bcd 100644 --- a/briar-core/src/org/briarproject/introduction/IntroducerManager.java +++ b/briar-core/src/org/briarproject/introduction/IntroducerManager.java @@ -31,6 +31,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID import static org.briarproject.api.introduction.IntroductionConstants.CONTACT_ID_2; import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_1; import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID_2; +import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME; import static org.briarproject.api.introduction.IntroductionConstants.MSG; import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY1; @@ -103,7 +104,7 @@ class IntroducerManager { return d; } - public void makeIntroduction(Transaction txn, Contact c1, Contact c2, + void makeIntroduction(Transaction txn, Contact c1, Contact c2, String msg, long timestamp) throws DbException, FormatException { // TODO check for existing session with those contacts? @@ -116,6 +117,9 @@ class IntroducerManager { BdfDictionary localAction = new BdfDictionary(); localAction.put(TYPE, TYPE_REQUEST); if (!StringUtils.isNullOrEmpty(msg)) { + int msgLength = StringUtils.toUtf8(msg).length; + if (msgLength > MAX_INTRODUCTION_MESSAGE_LENGTH) + throw new IllegalArgumentException(); localAction.put(MSG, msg); } localAction.put(PUBLIC_KEY1, c1.getAuthor().getPublicKey()); diff --git a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java index 3473ad61364779980c9868840ab3f9ad00f2c997..e2227e12eab424ab4fa2cead2ae29f70333e3240 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java @@ -21,6 +21,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; import static org.briarproject.api.introduction.IntroductionConstants.MAC; import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH; +import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID; import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME; import static org.briarproject.api.introduction.IntroductionConstants.MSG; @@ -37,7 +38,6 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE; import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT; import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; -import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; class IntroductionValidator extends BdfMessageValidator { @@ -92,7 +92,7 @@ class IntroductionValidator extends BdfMessageValidator { String msg = null; if (message.size() == 5) { msg = message.getString(4); - checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH); + checkLength(msg, 0, MAX_INTRODUCTION_MESSAGE_LENGTH); } // Return the metadata diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java index 7786dedee9cf585ec67e42c40c375b3d2b01d4b4..9248393b37c0118d9dc5ea293e075f9c248c3526 100644 --- a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java @@ -204,6 +204,7 @@ class BlogSharingManagerImpl extends .createBlog(msg.getBlogTitle(), msg.getBlogDesc(), author); } + @Override public Blog parse(BlogInviteeSessionState state) { Author author = authorFactory .createAuthor(state.getBlogAuthorName(), diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java b/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java index 1a9f0ada3897cc22be668afd7c47c44cf7ea6f34..5f0988254537f03769bc801c3f362a3d2c30a8e4 100644 --- a/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java +++ b/briar-core/src/org/briarproject/sharing/BlogSharingValidator.java @@ -16,14 +16,15 @@ import javax.inject.Inject; import static org.briarproject.api.blogs.BlogConstants.BLOG_AUTHOR_NAME; import static org.briarproject.api.blogs.BlogConstants.BLOG_DESC; -import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE; import static org.briarproject.api.blogs.BlogConstants.BLOG_PUBLIC_KEY; +import static org.briarproject.api.blogs.BlogConstants.BLOG_TITLE; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG; import static org.briarproject.api.sharing.SharingConstants.LOCAL; +import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; import static org.briarproject.api.sharing.SharingConstants.SESSION_ID; import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT; import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT; @@ -32,7 +33,6 @@ import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVIT import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE; import static org.briarproject.api.sharing.SharingConstants.TIME; import static org.briarproject.api.sharing.SharingConstants.TYPE; -import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; class BlogSharingValidator extends BdfMessageValidator { @@ -76,7 +76,7 @@ class BlogSharingValidator extends BdfMessageValidator { if (body.size() > 5) { String msg = body.getString(5); - checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH); + checkLength(msg, 0, MAX_INVITATION_MESSAGE_LENGTH); d.put(INVITATION_MSG, msg); } } else { diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java index c78cda5bcc22b4bde0bf255f89cc7128791e08b3..092cf96f58edee95ab989df89d01e0b34c8fe18c 100644 --- a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java @@ -167,6 +167,7 @@ class ForumSharingManagerImpl extends .createForum(msg.getForumName(), msg.getForumSalt()); } + @Override public Forum parse(ForumInviteeSessionState state) { return forumFactory .createForum(state.getForumName(), state.getForumSalt()); diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java b/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java index 702466bea05df11eb5887b354b41891ea25f4d80..11343f53803433a40675bb5ee916c27870ee8e17 100644 --- a/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java +++ b/briar-core/src/org/briarproject/sharing/ForumSharingValidator.java @@ -1,9 +1,9 @@ package org.briarproject.sharing; import org.briarproject.api.FormatException; +import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.clients.ClientHelper; import org.briarproject.api.clients.SessionId; -import org.briarproject.api.clients.BdfMessageContext; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataEncoder; @@ -20,6 +20,7 @@ import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; import static org.briarproject.api.sharing.SharingConstants.INVITATION_MSG; import static org.briarproject.api.sharing.SharingConstants.LOCAL; +import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; import static org.briarproject.api.sharing.SharingConstants.SESSION_ID; import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT; import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT; @@ -28,7 +29,6 @@ import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVIT import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE; import static org.briarproject.api.sharing.SharingConstants.TIME; import static org.briarproject.api.sharing.SharingConstants.TYPE; -import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; class ForumSharingValidator extends BdfMessageValidator { @@ -61,7 +61,7 @@ class ForumSharingValidator extends BdfMessageValidator { if (body.size() > 4) { String msg = body.getString(4); - checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH); + checkLength(msg, 0, MAX_INVITATION_MESSAGE_LENGTH); d.put(INVITATION_MSG, msg); } } else { diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index 9def8487dc2b82b803b6baff9b0092062c13dc61..a7c90d0a78f45edcf201803856d1cce078572517 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -29,7 +29,6 @@ import org.briarproject.api.sharing.InvitationItem; import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sharing.Shareable; import org.briarproject.api.sharing.SharingManager; -import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.Message; @@ -58,6 +57,7 @@ import static org.briarproject.api.clients.ProtocolEngine.StateUpdate; import static org.briarproject.api.sharing.SharingConstants.CONTACT_ID; import static org.briarproject.api.sharing.SharingConstants.IS_SHARER; import static org.briarproject.api.sharing.SharingConstants.LOCAL; +import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH; import static org.briarproject.api.sharing.SharingConstants.SESSION_ID; import static org.briarproject.api.sharing.SharingConstants.SHAREABLE_ID; import static org.briarproject.api.sharing.SharingConstants.SHARED_BY_US; @@ -115,8 +115,6 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS localGroup = contactGroupFactory.createLocalGroup(getClientId()); } - public abstract ClientId getClientId(); - protected abstract InvitationMessage createInvitationRequest(MessageId id, I msg, ContactId contactId, boolean available, long time, boolean local, boolean sent, boolean seen, boolean read); @@ -282,6 +280,9 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS // add invitation message to local state to be available for engine if (!StringUtils.isNullOrEmpty(msg)) { + int msgLength = StringUtils.toUtf8(msg).length; + if (msgLength > MAX_INVITATION_MESSAGE_LENGTH) + throw new IllegalArgumentException(); localState.setMessage(msg); } diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java index f8a97a6d635bd403c59eb9ea659decdde3c68f5a..2c67a81bb8c6002a56a9a2c9c07b6f41b7a3c01d 100644 --- a/briar-core/src/org/briarproject/util/StringUtils.java +++ b/briar-core/src/org/briarproject/util/StringUtils.java @@ -1,10 +1,18 @@ package org.briarproject.util; import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; import java.util.Collection; +import static java.nio.charset.CodingErrorAction.IGNORE; + public class StringUtils { + private static final Charset UTF_8 = Charset.forName("UTF-8"); + private static final char[] HEX = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' @@ -32,23 +40,29 @@ public class StringUtils { } public static String fromUtf8(byte[] bytes) { - try { - return new String(bytes, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + return fromUtf8(bytes, 0, bytes.length); } public static String fromUtf8(byte[] bytes, int off, int len) { + CharsetDecoder decoder = UTF_8.newDecoder(); + decoder.onMalformedInput(IGNORE); + decoder.onUnmappableCharacter(IGNORE); + ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len); try { - return new String(bytes, off, len, "UTF-8"); - } catch (UnsupportedEncodingException e) { + return decoder.decode(buffer).toString(); + } catch (CharacterCodingException e) { throw new RuntimeException(e); } } + public static String truncateUtf8(String s, int maxUtf8Length) { + byte[] utf8 = toUtf8(s); + if (utf8.length <= maxUtf8Length) return s; + return fromUtf8(utf8, 0, maxUtf8Length); + } + /** Converts the given byte array to a hex character array. */ - public static char[] toHexChars(byte[] bytes) { + private static char[] toHexChars(byte[] bytes) { char[] hex = new char[bytes.length * 2]; for (int i = 0, j = 0; i < bytes.length; i++) { hex[j++] = HEX[(bytes[i] >> 4) & 0xF]; diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java index af9b2723cc9d8bc4b2e5399a7a56c9beef434147..19ea3b62b07613bd96fb7d7330488857a2d02171 100644 --- a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java +++ b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java @@ -30,6 +30,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID; import static org.briarproject.api.introduction.IntroductionConstants.MAC; import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH; +import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH; import static org.briarproject.api.introduction.IntroductionConstants.MSG; import static org.briarproject.api.introduction.IntroductionConstants.NAME; import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY; @@ -83,8 +84,10 @@ public class IntroductionValidatorTest extends BriarTestCase { public void testValidateProperIntroductionRequest() throws IOException { final byte[] sessionId = TestUtils.getRandomId(); final String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH); - final byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); - final String text = TestUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH); + final byte[] publicKey = + TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH); + final String text = + TestUtils.getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH); BdfList body = BdfList.of(TYPE_REQUEST, sessionId, name, publicKey, text); @@ -107,14 +110,15 @@ public class IntroductionValidatorTest extends BriarTestCase { // no NAME is message BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID), - msg.getRaw(PUBLIC_KEY)); + msg.getRaw(PUBLIC_KEY)); if (msg.containsKey(MSG)) body.add(msg.getString(MSG)); validator.validateMessage(message, group, body); } @Test(expected = FormatException.class) - public void testValidateIntroductionRequestWithLongName() throws IOException { + public void testValidateIntroductionRequestWithLongName() + throws IOException { // too long NAME in message BdfDictionary msg = getValidIntroductionRequest(); msg.put(NAME, msg.get(NAME) + "x"); @@ -315,7 +319,8 @@ public class IntroductionValidatorTest extends BriarTestCase { } @Test(expected = FormatException.class) - public void testValidateIntroductionAckWithLongSessionId() throws IOException { + public void testValidateIntroductionAckWithLongSessionId() + throws IOException { BdfDictionary msg = BdfDictionary.of( new BdfEntry(TYPE, TYPE_ACK), new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1]) diff --git a/briar-tests/src/org/briarproject/util/StringUtilsTest.java b/briar-tests/src/org/briarproject/util/StringUtilsTest.java index 9ac929b3a3b31baca35ff352594ae8bb764b36f3..165b5bd8592b63d60dc3d9cb5a441a1e8e02696e 100644 --- a/briar-tests/src/org/briarproject/util/StringUtilsTest.java +++ b/briar-tests/src/org/briarproject/util/StringUtilsTest.java @@ -5,30 +5,172 @@ import org.junit.Test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertSame; public class StringUtilsTest extends BriarTestCase { @Test public void testToHexString() { - byte[] b = new byte[] {1, 2, 3, 127, -128}; - String s = StringUtils.toHexString(b); - assertEquals("0102037F80", s); - } - - @Test - public void testFromHexString() { - try { - StringUtils.fromHexString("12345"); - fail(); - } catch (IllegalArgumentException expected) {} - try { - StringUtils.fromHexString("ABCDEFGH"); - fail(); - } catch (IllegalArgumentException expected) {} - byte[] b = StringUtils.fromHexString("0102037F80"); - assertArrayEquals(new byte[] {1, 2, 3, 127, -128}, b); - b = StringUtils.fromHexString("0a0b0c0d0e0f"); - assertArrayEquals(new byte[] {10, 11, 12, 13, 14, 15}, b); + byte[] b = new byte[] { + 0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80, + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF + }; + String expected = "000102037F800A0B0C0D0EFF"; + assertEquals(expected, StringUtils.toHexString(b)); + } + + @Test + public void testToHexStringEmptyInput() { + assertEquals("", StringUtils.toHexString(new byte[0])); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromHexStringRejectsInvalidLength() { + StringUtils.fromHexString("12345"); + } + + @Test(expected = IllegalArgumentException.class) + public void testFromHexStringRejectsInvalidCharacter() { + StringUtils.fromHexString("ABCDEFGH"); + } + + @Test + public void testFromHexStringUppercase() { + String s = "000102037F800A0B0C0D0EFF"; + byte[] expected = new byte[] { + 0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80, + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF + }; + assertArrayEquals(expected, StringUtils.fromHexString(s)); + } + + @Test + public void testFromHexStringLowercase() { + String s = "000102037f800a0b0c0d0eff"; + byte[] expected = new byte[] { + 0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80, + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF + }; + assertArrayEquals(expected, StringUtils.fromHexString(s)); + } + + @Test + public void testFromHexStringEmptyInput() { + assertArrayEquals(new byte[0], StringUtils.fromHexString("")); + } + + @Test + public void testToUtf8EncodesNullCharacterAsStandardUtf8() { + // The Unicode null character should be encoded as a single null byte, + // not as two bytes as in CESU-8 and modified UTF-8 + String s = "\u0000"; + assertArrayEquals(new byte[1], StringUtils.toUtf8(s)); + } + + @Test + public void testToUtf8EncodesSupplementaryCharactersAsStandardUtf8() { + // A supplementary character should be encoded as four bytes, not as a + // surrogate pair as in CESU-8 and modified UTF-8 + String s = "\u0045\u0205\uD801\uDC00"; + byte[] expected = new byte[] { + 0x45, // U+0045 + (byte) 0xC8, (byte) 0x85, // U+0205 + (byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80 // U+10400 + }; + assertArrayEquals(expected, StringUtils.toUtf8(s)); + } + + @Test + public void testToUtf8EmptyInput() { + assertArrayEquals(new byte[0], StringUtils.toUtf8("")); + } + + @Test + public void testFromUtf8AcceptsNullCharacterUsingStandardUtf8() { + // The UTF-8 encoding of the null character is valid + assertEquals("\u0000", StringUtils.fromUtf8(new byte[1])); + } + + @Test + public void testFromUtf8RemovesNullCharacterUsingModifiedUtf8() { + // The modified UTF-8 encoding of the null character is not valid + byte[] b = new byte[] { + (byte) 0xC0, (byte) 0x80, // Null character as modified UTF-8 + (byte) 0xC8, (byte) 0x85 // U+0205 + }; + // Conversion should ignore the invalid character and return the rest + String expected = "\u0205"; + assertEquals(expected, StringUtils.fromUtf8(b)); + } + + @Test + public void testFromUtf8AcceptsSupplementaryCharacterUsingStandardUtf8() { + // The UTF-8 encoding of a supplementary character is valid and should + // be converted to a surrogate pair + byte[] b = new byte[] { + (byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80, // U+10400 + (byte) 0xC8, (byte) 0x85 // U+0205 + }; + String expected = "\uD801\uDC00\u0205"; // Surrogate pair + assertEquals(expected, StringUtils.fromUtf8(b)); + } + + @Test + public void testFromUtf8RemovesSupplementaryCharacterUsingModifiedUtf8() { + // The CESU-8 or modified UTF-8 encoding of a supplementary character + // is not valid + byte[] b = new byte[] { + (byte) 0xED, (byte) 0xA0, (byte) 0x81, // U+10400 as CSEU-8 + (byte) 0xED, (byte) 0xB0, (byte) 0x80, + (byte) 0xC8, (byte) 0x85 // U+0205 + }; + // Converstion should ignore the invalid character and return the rest + String expected = "\u0205"; + assertEquals(expected, StringUtils.fromUtf8(b)); + } + + @Test + public void testFromUtf8EmptyInput() { + assertEquals("", StringUtils.fromUtf8(new byte[0])); + } + + @Test + public void testTruncateUtf8ReturnsArgumentIfNotTruncated() { + String s = "Hello"; + assertSame(s, StringUtils.truncateUtf8(s, 5)); + } + + @Test + public void testTruncateUtf8ChecksUtf8LengthNotStringLength() { + String s = "H\u0205llo"; + assertEquals(5, s.length()); + assertEquals(6, StringUtils.toUtf8(s).length); + String expected = "H\u0205ll"; // Sixth byte removed + assertEquals(expected, StringUtils.truncateUtf8(s, 5)); + } + + @Test + public void testTruncateUtf8RemovesTruncatedCharacter() { + String s = "\u0205\u0205"; // String requires four bytes + String expected = "\u0205"; // Partial character removed + String truncated = StringUtils.truncateUtf8(s, 3); + assertEquals(expected, truncated); + // Converting the truncated string should not exceed the max length + assertEquals(2, StringUtils.toUtf8(truncated).length); + } + + @Test + public void testTruncateUtf8RemovesTruncatedSurrogatePair() { + String s = "\u0205\uD801\uDC00"; // String requires six bytes + String expected = "\u0205"; // Partial character removed + String truncated = StringUtils.truncateUtf8(s, 3); + assertEquals(expected, truncated); + // Converting the truncated string should not exceed the max length + assertEquals(2, StringUtils.toUtf8(truncated).length); + } + + @Test + public void testTruncateUtf8EmptyInput() { + assertEquals("", StringUtils.truncateUtf8("", 123)); } }