diff options
Diffstat (limited to 'src/main/java/eu')
7 files changed, 129 insertions, 27 deletions
| diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java index c30f3df..06ea299 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkText.java @@ -1,7 +1,6 @@  package eu.mulk.mulkcms2.benki.bookmarks;  import eu.mulk.mulkcms2.benki.posts.PostText; -import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;  import javax.annotation.CheckForNull;  import javax.persistence.Column;  import javax.persistence.Entity; @@ -22,10 +21,7 @@ public class BookmarkText extends PostText<Bookmark> {    @Transient    @CheckForNull -  protected String computeDescriptionHtml() { -    if (description == null) { -      return null; -    } -    return new MarkdownConverter().htmlify(description); +  protected String getDescriptionMarkup() { +    return description;    }  } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java index 63f4791..7f6ed3b 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessage.java @@ -21,11 +21,7 @@ public class LazychatMessage extends Post<LazychatMessageText> {        joinColumns = {@JoinColumn(name = "referrer")},        inverseJoinColumns = {@JoinColumn(name = "referee")})    @JsonbTransient -  public Collection<LazychatMessage> referees; - -  @ManyToMany(mappedBy = "referees") -  @JsonbTransient -  public Collection<LazychatMessage> referrers; +  public Collection<Post<?>> referees;    @CheckForNull    @Override diff --git a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java index 1a60877..72bb983 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/lazychat/LazychatMessageText.java @@ -1,7 +1,6 @@  package eu.mulk.mulkcms2.benki.lazychat;  import eu.mulk.mulkcms2.benki.posts.PostText; -import eu.mulk.mulkcms2.common.markdown.MarkdownConverter;  import javax.annotation.CheckForNull;  import javax.json.bind.annotation.JsonbTransient;  import javax.persistence.Column; @@ -19,10 +18,7 @@ public class LazychatMessageText extends PostText<LazychatMessage> {    @CheckForNull    @Override    @JsonbTransient -  protected String computeDescriptionHtml() { -    if (content == null) { -      return null; -    } -    return new MarkdownConverter().htmlify(content); +  protected String getDescriptionMarkup() { +    return content;    }  } diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java index 2c56285..d3e7712 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/Post.java @@ -109,6 +109,10 @@ public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {    @JsonbTransient    public Set<Role> targets; +  @ManyToMany(mappedBy = "referees") +  @JsonbTransient +  public Collection<LazychatMessage> referrers; +    @OneToMany(        mappedBy = "post",        fetch = FetchType.LAZY, @@ -389,6 +393,16 @@ public abstract class Post<Text extends PostText<?>> extends PanacheEntityBase {      }    } +  public Collection<LazychatMessage> getComments() { +    return referrers.stream() +        .filter(l -> l.scope == Scope.comment) +        .sorted( +            Comparator.comparing( +                    (LazychatMessage l) -> Objects.requireNonNullElse(l.date, OffsetDateTime.MIN)) +                .reversed()) +        .toList(); +  } +    public enum Visibility {      PUBLIC,      SEMIPRIVATE, diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java index 24a564b..5a38262 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java @@ -4,6 +4,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;  import static javax.ws.rs.core.MediaType.APPLICATION_ATOM_XML;  import static javax.ws.rs.core.MediaType.APPLICATION_JSON;  import static javax.ws.rs.core.MediaType.TEXT_HTML; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN;  import com.blazebit.persistence.CriteriaBuilderFactory;  import com.rometools.rome.feed.atom.Content; @@ -15,8 +16,10 @@ import com.rometools.rome.io.FeedException;  import com.rometools.rome.io.WireFeedOutput;  import eu.mulk.mulkcms2.benki.accesscontrol.PageKey;  import eu.mulk.mulkcms2.benki.accesscontrol.Role; +import eu.mulk.mulkcms2.benki.lazychat.LazychatMessage;  import eu.mulk.mulkcms2.benki.login.LoginRoles;  import eu.mulk.mulkcms2.benki.posts.Post.PostPage; +import eu.mulk.mulkcms2.benki.posts.Post.Scope;  import eu.mulk.mulkcms2.benki.users.User;  import io.quarkus.qute.CheckedTemplate;  import io.quarkus.qute.TemplateExtension; @@ -25,6 +28,7 @@ import io.quarkus.security.identity.SecurityIdentity;  import java.math.BigInteger;  import java.net.URI;  import java.net.URLEncoder; +import java.security.MessageDigest;  import java.security.NoSuchAlgorithmException;  import java.security.SecureRandom;  import java.time.Instant; @@ -49,14 +53,20 @@ import javax.json.spi.JsonProvider;  import javax.persistence.EntityManager;  import javax.persistence.PersistenceContext;  import javax.transaction.Transactional; +import javax.validation.constraints.NotEmpty;  import javax.ws.rs.BadRequestException;  import javax.ws.rs.ForbiddenException; +import javax.ws.rs.FormParam;  import javax.ws.rs.GET; +import javax.ws.rs.POST;  import javax.ws.rs.Path;  import javax.ws.rs.PathParam;  import javax.ws.rs.Produces;  import javax.ws.rs.QueryParam;  import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriBuilder;  import javax.ws.rs.core.UriInfo;  import org.eclipse.microprofile.config.inject.ConfigProperty;  import org.hibernate.Session; @@ -75,6 +85,8 @@ public abstract class PostResource {    private static final DateTimeFormatter humanDateFormatter =        DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG); +  private static final String hashcashDigestAlgorithm = "SHA-256"; +    private static final int pageKeyBytes = 32;    private static final int AUTOTITLE_WORDS = 10; @@ -283,6 +295,48 @@ public abstract class PostResource {      return makeFeed(pageKey, ownerName, owner);    } +  @POST +  @Produces(TEXT_PLAIN) +  @Path("{id}/comments") +  @Transactional +  public Response postComment( +      @PathParam("id") int postId, +      @FormParam("message") @NotEmpty String message, +      @FormParam("hashcash-salt") long hashcashSalt) +      throws NoSuchAlgorithmException { +    var hashcashDigest = MessageDigest.getInstance(hashcashDigestAlgorithm); +    hashcashDigest.update("Hashcash-Salt: ".getBytes(UTF_8)); +    hashcashDigest.update(String.valueOf(hashcashSalt).getBytes(UTF_8)); +    hashcashDigest.update("\n\n".getBytes(UTF_8)); + +    for (byte b : message.getBytes(UTF_8)) { +      if (b == '\r') { +        // Skip CR characters.  The JavaScript side does not include them in its computation. +        continue; +      } +      hashcashDigest.update(b); +    } +    var hashcash = hashcashDigest.digest(); + +    if (hashcash[0] != 0 || hashcash[1] != 0) { +      throw new BadRequestException( +          "invalid hashcash", +          Response.status(Status.BAD_REQUEST).entity("invalid hashcash").build()); +    } + +    Post<?> post = Post.findById(postId); + +    var comment = new LazychatMessage(); +    comment.date = OffsetDateTime.now(); +    comment.scope = Scope.comment; +    comment.referees = List.of(post); +    comment.setContent(message); +    assignPostTargets(post.getVisibility(), post.owner, comment); +    comment.persist(); + +    return Response.seeOther(UriBuilder.fromUri("/posts/{id}").build(postId)).build(); +  } +    private String makeFeed(        @CheckForNull BigInteger pageKey, @CheckForNull String ownerName, @CheckForNull User owner)        throws FeedException { diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java index 11ac98a..80971b1 100644 --- a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java +++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostText.java @@ -1,6 +1,9 @@  package eu.mulk.mulkcms2.benki.posts;  import com.vladmihalcea.hibernate.type.search.PostgreSQLTSVectorType; +import eu.mulk.mulkcms2.benki.posts.Post.Scope; +import eu.mulk.mulkcms2.common.markdown.MarkdownConverter; +import eu.mulk.mulkcms2.common.markdown.MarkdownConverter.Mode;  import io.quarkus.hibernate.orm.panache.PanacheEntityBase;  import javax.annotation.CheckForNull;  import javax.json.bind.annotation.JsonbTransient; @@ -69,5 +72,15 @@ public abstract class PostText<OwningPost extends Post<?>> extends PanacheEntity    }    @CheckForNull -  protected abstract String computeDescriptionHtml(); +  protected abstract String getDescriptionMarkup(); + +  @CheckForNull +  private String computeDescriptionHtml() { +    var markup = getDescriptionMarkup(); +    if (markup == null) { +      return null; +    } +    return new MarkdownConverter(post.scope == Scope.top_level ? Mode.POST : Mode.COMMENT) +        .htmlify(markup); +  }  } diff --git a/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java b/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java index 859fd71..2a144c5 100644 --- a/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java +++ b/src/main/java/eu/mulk/mulkcms2/common/markdown/MarkdownConverter.java @@ -11,18 +11,22 @@ import com.vladsch.flexmark.html.HtmlRenderer;  import com.vladsch.flexmark.parser.Parser;  import com.vladsch.flexmark.util.data.MutableDataSet;  import java.util.Arrays; -import javax.enterprise.context.ApplicationScoped;  import org.jsoup.Jsoup;  import org.jsoup.safety.Cleaner;  import org.jsoup.safety.Safelist; -@ApplicationScoped  public class MarkdownConverter { +  public enum Mode { +    POST, +    COMMENT, +  } +    private final Parser parser;    private final HtmlRenderer renderer; +  private final Mode mode; -  public MarkdownConverter() { +  public MarkdownConverter(Mode mode) {      var options = new MutableDataSet();      options.set(          Parser.EXTENSIONS, @@ -42,6 +46,7 @@ public class MarkdownConverter {      options.set(TypographicExtension.ENABLE_QUOTES, true);      options.set(FootnoteExtension.FOOTNOTE_BACK_REF_STRING, ""); +    this.mode = mode;      this.parser = Parser.builder(options).build();      this.renderer = HtmlRenderer.builder(options).build();    } @@ -49,14 +54,42 @@ public class MarkdownConverter {    public String htmlify(String markdown) {      var parsedDocument = parser.parse(markdown);      var uncleanHtml = renderer.render(parsedDocument); -    var cleaner = -        new Cleaner( -            Safelist.relaxed() -                .addTags("abbr", "acronym") -                .addAttributes("abbr", "title") -                .addAttributes("acronym", "title")); +    var cleaner = makeCleaner();      var cleanedDocument = cleaner.clean(Jsoup.parseBodyFragment(uncleanHtml));      cleanedDocument.select("table").addClass("pure-table").addClass("pure-table-horizontal");      return cleanedDocument.body().html();    } + +  private Cleaner makeCleaner() { +    var safelist = +        switch (mode) { +          case POST -> Safelist.relaxed() +              .addTags("abbr", "acronym") +              .addAttributes("abbr", "title") +              .addAttributes("acronym", "title"); +          case COMMENT -> Safelist.simpleText() +              .addTags( +                  "p", +                  "blockquote", +                  "cite", +                  "code", +                  "pre", +                  "dd", +                  "dl", +                  "dt", +                  "s", +                  "sub", +                  "sup", +                  "ol", +                  "ul", +                  "li", +                  "abbr", +                  "acronym", +                  "ins", +                  "del") +              .addAttributes("abbr", "title") +              .addAttributes("acronym", "title"); +        }; +    return new Cleaner(safelist); +  }  } | 
