FIX: Link uploads for encrypted posts.

FIX: Link uploads for encrypted posts.

diff --git a/assets/javascripts/discourse/initializers/hook-composer.js.es6 b/assets/javascripts/discourse/initializers/hook-composer.js.es6
index b3b5f3c..29808f7 100644
--- a/assets/javascripts/discourse/initializers/hook-composer.js.es6
+++ b/assets/javascripts/discourse/initializers/hook-composer.js.es6
@@ -7,6 +7,7 @@ import {
 } from "discourse/plugins/discourse-encrypt/lib/keys";
 import {
   ENCRYPT_ACTIVE,
+  decryptPost,
   getEncryptionStatus,
   getRsaKey,
   getTopicKey,
@@ -122,7 +123,7 @@ export default {
           const topicId = model.get("topic.id");
           decTitle = getTopicTitle(topicId);
           decReply = getTopicKey(topicId).then(key =>
-            decrypt(key, model.reply)
+            decryptPost(key, model.reply)
           );
         } else {
           const pos = model.reply ? model.reply.indexOf("\n") : -1;
@@ -139,7 +140,7 @@ export default {
               : "";
 
             decReply = model.reply
-              ? decKey.then(key => decrypt(key, model.reply))
+              ? decKey.then(key => decryptPost(key, model.reply))
               : "";
           }
         }
diff --git a/assets/javascripts/discourse/initializers/hook-decrypt-post.js.es6 b/assets/javascripts/discourse/initializers/hook-decrypt-post.js.es6
index 1e0f19b..604a4eb 100644
--- a/assets/javascripts/discourse/initializers/hook-decrypt-post.js.es6
+++ b/assets/javascripts/discourse/initializers/hook-decrypt-post.js.es6
@@ -5,9 +5,9 @@ import { ajax } from "discourse/lib/ajax";
 import showModal from "discourse/lib/show-modal";
 import { renderSpinner } from "discourse/helpers/loading-spinner";
 import { iconHTML } from "discourse-common/lib/icon-library";
-import { decrypt } from "discourse/plugins/discourse-encrypt/lib/keys";
 import {
   ENCRYPT_DISABLED,
+  decryptPost,
   getEncryptionStatus,
   getRsaKey,
   getTopicKey,
@@ -44,7 +44,7 @@ export default {
             getRsaKey()
               .then(() =>
                 getTopicKey(topicId)
-                  .then(key => decrypt(key, ciphertext))
+                  .then(key => decryptPost(key, ciphertext))
                   .then(plaintext => cookAsync(plaintext))
                   .then(cooked => {
                     state.decrypting = false;
diff --git a/assets/javascripts/discourse/initializers/hook-decrypt-revision.js.es6 b/assets/javascripts/discourse/initializers/hook-decrypt-revision.js.es6
index 7c2e00d..4eca346 100644
--- a/assets/javascripts/discourse/initializers/hook-decrypt-revision.js.es6
+++ b/assets/javascripts/discourse/initializers/hook-decrypt-revision.js.es6
@@ -1,8 +1,8 @@
 import Post from "discourse/models/post";
 import { cookAsync } from "discourse/lib/text";
-import { decrypt } from "discourse/plugins/discourse-encrypt/lib/keys";
 import {
   ENCRYPT_ACTIVE,
+  decryptPost,
   getEncryptionStatus,
   getTopicKey,
   hasTopicKey
@@ -26,8 +26,8 @@ export default {
 
           const topicKey = getTopicKey(result.topic_id);
           return Promise.all([
-            topicKey.then(k => decrypt(k, result.raws.previous)),
-            topicKey.then(k => decrypt(k, result.raws.current))
+            topicKey.then(k => decryptPost(k, result.raws.previous)),
+            topicKey.then(k => decryptPost(k, result.raws.current))
           ])
             .then(([previous, current]) =>
               Promise.all([
diff --git a/assets/javascripts/discourse/initializers/hook-draft.js.es6 b/assets/javascripts/discourse/initializers/hook-draft.js.es6
index 8265834..ac9806d 100644
--- a/assets/javascripts/discourse/initializers/hook-draft.js.es6
+++ b/assets/javascripts/discourse/initializers/hook-draft.js.es6
@@ -6,10 +6,11 @@ import {
   generateKey
 } from "discourse/plugins/discourse-encrypt/lib/keys";
 import {
-  hasTopicKey,
-  getRsaKey,
+  ENCRYPT_ACTIVE,
+  encryptPost,
   getEncryptionStatus,
-  ENCRYPT_ACTIVE
+  getRsaKey,
+  hasTopicKey
 } from "discourse/plugins/discourse-encrypt/lib/discourse";
 import { filterObjectKeys } from "discourse/plugins/discourse-encrypt/lib/utils";
 
@@ -67,7 +68,7 @@ export default {
             : "";
 
           const encReply = data.reply
-            ? topicKey.then(key => encrypt(key, data.reply))
+            ? topicKey.then(key => encryptPost(key, data.reply))
             : "";
 
           return Ember.RSVP.Promise.all([encTitle, encReply, encKey]).then(
diff --git a/assets/javascripts/discourse/initializers/hook-save.js.es6 b/assets/javascripts/discourse/initializers/hook-save.js.es6
index 461f3fd..bc7068b 100644
--- a/assets/javascripts/discourse/initializers/hook-save.js.es6
+++ b/assets/javascripts/discourse/initializers/hook-save.js.es6
@@ -9,6 +9,7 @@ import {
 } from "discourse/plugins/discourse-encrypt/lib/keys";
 import {
   ENCRYPT_ACTIVE,
+  encryptPost,
   getEncryptionStatus,
   hasTopicKey,
   putTopicKey
@@ -65,10 +66,10 @@ export default {
 
         let replyPromise = args.raw
           ? topicKey
-              .then(key => encrypt(key, args.raw))
+              .then(key => encryptPost(key, args.raw))
               .then(encryptedRaw => {
-                args.raw = I18n.t("encrypt.encrypted_topic_raw");
                 args.encrypted_raw = encryptedRaw;
+                args.raw = I18n.t("encrypt.encrypted_topic_raw");
               })
           : Ember.RSVP.Promise.resolve();
 
@@ -138,7 +139,7 @@ export default {
         }
 
         return getTopicKey(attrs.topic_id)
-          .then(key => encrypt(key, attrs.raw))
+          .then(key => encryptPost(key, attrs.raw))
           .then(encryptedRaw => {
             attrs.cooked = undefined;
             attrs.raw = encryptedRaw;
diff --git a/assets/javascripts/lib/discourse.js.es6 b/assets/javascripts/lib/discourse.js.es6
index 7c5e0a3..c7a67bb 100644
--- a/assets/javascripts/lib/discourse.js.es6
+++ b/assets/javascripts/lib/discourse.js.es6
@@ -1,6 +1,7 @@
 import {
   importKey,
-  decrypt
+  decrypt,
+  encrypt
 } from "discourse/plugins/discourse-encrypt/lib/keys";
 import {
   DB_NAME,
@@ -152,6 +153,32 @@ export function hasTopicTitle(topicId) {
 }
 
 /**
+ * Encrypts and wraps a post's raw text with extra information.
+ *
+ * The extra information will not be encrypted.
+ *
+ * For example, post uploads need to be visible in the text so the server
+ * does not attempt to remove them.
+ */
+export function encryptPost(key, plaintext, fn) {
+  let extra = "";
+
+  const uploads = plaintext.match(/upload:\/\/[A-Za-z0-9]{27,27}/g);
+  if (uploads) {
+    extra += "\n" + uploads.map(upload => `[](${upload})`).join();
+  }
+
+  return encrypt(key, plaintext).then(encrypted => encrypted + extra);
+}
+
+/**
+ * Unwraps and decrypts an encrypted post.
+ */
+export function decryptPost(key, ciphertext) {
+  return decrypt(key, ciphertext.split("\n")[0]);
+}
+
+/**
  * Gets current encryption status.
  *
  * @param user
diff --git a/plugin.rb b/plugin.rb
index 19d405c..64a1556 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -122,17 +122,15 @@ after_initialize do
   CategoryList.preloaded_topic_custom_fields << 'encrypted_title'
 
   # Hide cooked content.
-  Plugin::Filter.register(:after_post_cook) do |post, cooked|
-    if post.is_encrypted? && post.raw.match(/\A[A-Za-z0-9+\\\/=]+\Z/)
-      next "<p>#{I18n.t('js.encrypt.encrypted_post')}</p>"
+  on(:post_process_cooked) do |doc, post|
+    if post.is_encrypted? && post.raw.match(/\A[A-Za-z0-9+\\\/=]+(\n.*)?\Z/)
+      doc.inner_html = "<p>#{I18n.t('js.encrypt.encrypted_post')}</p>"
     end
-
-    cooked
   end
 
   # Hide cooked content in email.
   on(:reduce_cooked) do |fragment, post|
-    if post && post.is_encrypted? && post.raw.match(/\A[A-Za-z0-9+\\\/=]+\Z/)
+    if post && post.is_encrypted? && post.raw.match(/\A[A-Za-z0-9+\\\/=]+(\n.*)?\Z/)
       fragment.inner_html = "<p>#{I18n.t('js.encrypt.encrypted_post_email')}</p>"
     end
   end

GitHub sha: 42696525