From 441d6776447f17140b6499362be7e3e6b1f0397d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 8 Dec 2023 14:46:51 +0100 Subject: [PATCH] Initial support for XEP-0446: File Metadata Element Also adds initial support for XEP-0264: Jingle Content Thumbnails Solves SMACK-894 --- .../element/FileMetadataElement.java | 312 ++++++++++++++++++ .../file_metadata/element/package-info.java | 20 ++ .../smackx/file_metadata/package-info.java | 20 ++ .../provider/FileMetadataElementProvider.java | 96 ++++++ .../file_metadata/provider/package-info.java | 20 ++ .../thumbnails/element/ThumbnailElement.java | 92 ++++++ .../thumbnails/element/package-info.java | 20 ++ .../provider/ThumbnailElementProvider.java | 45 +++ .../thumbnails/provider/package-info.java | 20 ++ .../experimental.providers | 14 + .../FileMetadataElementTest.java | 167 ++++++++++ .../element/ThumbnailElementTest.java | 56 ++++ .../ThumbnailElementProviderTest.java | 64 ++++ 13 files changed, 946 insertions(+) create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/FileMetadataElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/FileMetadataElementProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/package-info.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/file_metadata/FileMetadataElementTest.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElementTest.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProviderTest.java diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/FileMetadataElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/FileMetadataElement.java new file mode 100644 index 000000000..e9f2976bb --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/FileMetadataElement.java @@ -0,0 +1,312 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.file_metadata.element; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.CollectionUtil; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.HashCode; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.thumbnails.element.ThumbnailElement; + +/** + * File metadata element as defined in XEP-0446: File Metadata Element. + * This element is used in a generic way to provide information about files, e.g. during file sharing. + */ +public final class FileMetadataElement implements ExtensionElement { + + public static final String ELEMENT = "file"; + public static final String NAMESPACE = "urn:xmpp:file:metadata:0"; + public static final String ELEM_DATE = "date"; + public static final String ELEM_HEIGHT = "height"; + public static final String ELEM_WIDTH = "width"; + public static final String ELEM_DESC = "desc"; + public static final String ELEM_LENGTH = "length"; + public static final String ELEM_MEDIA_TYPE = "media-type"; + public static final String ELEM_NAME = "name"; + public static final String ELEM_SIZE = "size"; + + + private final Date date; + private final Integer height; + private final Integer width; + private final Map descriptions; + private final Map hashElements; + private final Long length; + private final String mediaType; + private final String name; + private final Long size; + private final List thumbnails; + + private FileMetadataElement(Date date, Integer height, Integer width, Map descriptions, + Map hashElements, Long length, + String mediaType, String name, Long size, + List thumbnails) { + this.date = date; + this.height = height; + this.width = width; + this.descriptions = CollectionUtil.cloneAndSeal(descriptions); + this.hashElements = CollectionUtil.cloneAndSeal(hashElements); + this.length = length; + this.mediaType = mediaType; + this.name = name; + this.size = size; + this.thumbnails = CollectionUtil.cloneAndSeal(thumbnails); + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder sb = new XmlStringBuilder(this) + .rightAngleBracket() + .optElement(ELEM_DATE, date) + .optElement(ELEM_HEIGHT, height) + .optElement(ELEM_WIDTH, width); + for (String key : descriptions.keySet()) { + sb.halfOpenElement(ELEM_DESC) + .optXmlLangAttribute(key) + .rightAngleBracket() + .append(descriptions.get(key)) + .closeElement(ELEM_DESC); + } + sb.append(hashElements.values()) + .optElement(ELEM_LENGTH, length != null ? Long.toString(length) : null) + .optElement(ELEM_MEDIA_TYPE, mediaType) + .optElement(ELEM_NAME, name) + .optElement(ELEM_SIZE, size != null ? Long.toString(size) : null) + .append(thumbnails); + return sb.closeElement(this); + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + public Date getDate() { + return date; + } + + public Integer getHeight() { + return height; + } + + public Integer getWidth() { + return width; + } + + public Map getDescriptions() { + return Collections.unmodifiableMap(descriptions); + } + + public String getDescription() { + return getDescription(getLanguage()); + } + + public String getDescription(String lang) { + return descriptions.get(lang != null ? lang : ""); + } + + public Map getHashElements() { + return Collections.unmodifiableMap(hashElements); + } + + public HashElement getHashElement(HashManager.ALGORITHM algorithm) { + return hashElements.get(algorithm); + } + + public Long getLength() { + return length; + } + + public String getMediaType() { + return mediaType; + } + + /** + * Return the name of the file. + * + * @return escaped name + */ + public String getName() { + if (name == null) { + return null; + } + try { + return URLEncoder.encode(name, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); // UTF-8 MUST be supported + } + } + + public String getRawName() { + return name; + } + + public Long getSize() { + return size; + } + + public List getThumbnails() { + return Collections.unmodifiableList(thumbnails); + } + + @Override + public int hashCode() { + return HashCode.builder() + .append(getElementName()) + .append(getNamespace()) + .append(getDate()) + .append(getDescriptions()) + .append(getHeight()) + .append(getWidth()) + .append(getHashElements()) + .append(getLength()) + .append(getMediaType()) + .append(getRawName()) + .append(getSize()) + .append(getThumbnails()) + .build(); + } + + @Override + public boolean equals(Object other) { + return EqualsUtil.equals(this, other, (equalsBuilder, o) -> equalsBuilder + .append(getElementName(), o.getElementName()) + .append(getNamespace(), o.getNamespace()) + .append(getDate(), o.getDate()) + .append(getDescriptions(), o.getDescriptions()) + .append(getHeight(), o.getHeight()) + .append(getWidth(), o.getWidth()) + .append(getHashElements(), o.getHashElements()) + .append(getLength(), o.getLength()) + .append(getMediaType(), o.getMediaType()) + .append(getRawName(), o.getRawName()) + .append(getSize(), o.getSize()) + .append(getThumbnails(), o.getThumbnails())); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Date date; + private Integer height; + private Integer width; + private Map descriptions = new HashMap<>(); + private Map hashElements = new HashMap<>(); + private Long length; + private String mediaType; + private String name; + private Long size; + private List thumbnails = new ArrayList<>(); + + public Builder setModificationDate(Date date) { + this.date = date; + return this; + } + + public Builder setDimensions(int width, int height) { + return setHeight(height).setWidth(width); + } + + public Builder setHeight(int height) { + if (height <= 0) { + throw new IllegalArgumentException("Height must be a positive number"); + } + this.height = height; + return this; + } + + public Builder setWidth(int width) { + if (width <= 0) { + throw new IllegalArgumentException("Width must be a positive number"); + } + this.width = width; + return this; + } + + public Builder addDescription(String description) { + return addDescription(description, null); + } + + public Builder addDescription(String description, String language) { + this.descriptions.put(language != null ? language : "", StringUtils.requireNotNullNorEmpty(description, "Description MUST NOT be null nor empty")); + return this; + } + + public Builder addHash(HashElement hashElement) { + hashElements.put(hashElement.getAlgorithm(), hashElement); + return this; + } + + public Builder setLength(long length) { + if (length < 0) { + throw new IllegalArgumentException("Length cannot be negative."); + } + this.length = length; + return this; + } + + public Builder setMediaType(String mediaType) { + this.mediaType = StringUtils.requireNotNullNorEmpty(mediaType, "Media-Type MUST NOT be null nor empty"); + return this; + } + + public Builder setName(String name) { + this.name = StringUtils.requireNotNullNorEmpty(name, "Name MUST NOT be null nor empty"); + return this; + } + + public Builder setSize(long size) { + if (size < 0) { + throw new IllegalArgumentException("Size MUST NOT be negative."); + } + this.size = size; + return this; + } + + public Builder addThumbnail(ThumbnailElement thumbnail) { + thumbnails.add(thumbnail); + return this; + } + + public FileMetadataElement build() { + return new FileMetadataElement(date, height, width, descriptions, hashElements, length, + mediaType, name, size, thumbnails); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/package-info.java new file mode 100644 index 000000000..dbea97273 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/element/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Smacks implementation of XEP-0446: File Metadata Element. + */ +package org.jivesoftware.smackx.file_metadata.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/package-info.java new file mode 100644 index 000000000..8d9825d6c --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Smacks implementation of XEP-0446: File Metadata Element. + */ +package org.jivesoftware.smackx.file_metadata; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/FileMetadataElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/FileMetadataElementProvider.java new file mode 100644 index 000000000..cd87dcdb4 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/FileMetadataElementProvider.java @@ -0,0 +1,96 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.file_metadata.provider; + +import java.io.IOException; +import java.text.ParseException; + +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.file_metadata.element.FileMetadataElement; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.hashes.provider.HashElementProvider; +import org.jivesoftware.smackx.thumbnails.element.ThumbnailElement; +import org.jivesoftware.smackx.thumbnails.provider.ThumbnailElementProvider; + +public class FileMetadataElementProvider extends ExtensionElementProvider { + + public static FileMetadataElementProvider TEST_INSTANCE = new FileMetadataElementProvider(); + + @Override + public FileMetadataElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws XmlPullParserException, IOException, SmackParsingException, ParseException { + FileMetadataElement.Builder builder = FileMetadataElement.builder(); + do { + XmlPullParser.TagEvent tagEvent = parser.nextTag(); + String name = parser.getName(); + if (tagEvent != XmlPullParser.TagEvent.START_ELEMENT) { + continue; + } + switch (name) { + case FileMetadataElement.ELEMENT: + parser.next(); + break; + case FileMetadataElement.ELEM_DATE: + builder.setModificationDate(ParserUtils.getDateFromNextText(parser)); + break; + case FileMetadataElement.ELEM_DESC: + String lang = ParserUtils.getXmlLang(parser); + builder.addDescription(ParserUtils.getRequiredNextText(parser), lang); + break; + case "dimensions": // was replaced with width and height + String dimensions = ParserUtils.getRequiredNextText(parser); + String[] split = dimensions.split("x"); + if (split.length != 2) { + throw new IllegalArgumentException("Invalid dimensions."); + } + builder.setWidth(Integer.parseInt(split[0])); + builder.setHeight(Integer.parseInt(split[1])); + break; + case FileMetadataElement.ELEM_WIDTH: + builder.setWidth(Integer.parseInt(ParserUtils.getRequiredNextText(parser))); + break; + case FileMetadataElement.ELEM_HEIGHT: + builder.setHeight(Integer.parseInt(ParserUtils.getRequiredNextText(parser))); + break; + case FileMetadataElement.ELEM_LENGTH: + builder.setLength(Long.parseLong(ParserUtils.getRequiredNextText(parser))); + break; + case FileMetadataElement.ELEM_MEDIA_TYPE: + builder.setMediaType(ParserUtils.getRequiredNextText(parser)); + break; + case FileMetadataElement.ELEM_NAME: + builder.setName(ParserUtils.getRequiredNextText(parser)); + break; + case FileMetadataElement.ELEM_SIZE: + builder.setSize(Long.parseLong(ParserUtils.getRequiredNextText(parser))); + break; + case HashElement.ELEMENT: + builder.addHash(HashElementProvider.INSTANCE.parse(parser, parser.getDepth(), xmlEnvironment)); + break; + case ThumbnailElement.ELEMENT: + ThumbnailElementProvider provider = new ThumbnailElementProvider(); + builder.addThumbnail(provider.parse(parser, parser.getDepth(), xmlEnvironment)); + } + } while (parser.getDepth() != initialDepth); + return builder.build(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/package-info.java new file mode 100644 index 000000000..e28f0d94b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/file_metadata/provider/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * File metadata element provider. + */ +package org.jivesoftware.smackx.file_metadata.provider; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElement.java new file mode 100644 index 000000000..3bcd4b045 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElement.java @@ -0,0 +1,92 @@ +/** + * + * Copyright 2023 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.thumbnails.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class ThumbnailElement implements ExtensionElement { + + public static final String ELEMENT = "thumbnail"; + public static final String NAMESPACE = "urn:xmpp:thumbs:1"; + public static final String ELEM_URI = "uri"; + public static final String ELEM_MEDIA_TYPE = "media-type"; + public static final String ELEM_WIDTH = "width"; + public static final String ELEM_HEIGHT = "height"; + + private final String uri; + private final String mediaType; + private final Integer width; + private final Integer height; + + public ThumbnailElement(String uri) { + this(uri, null, null, null); + } + + public ThumbnailElement(String uri, String mediaType, Integer width, Integer height) { + this.uri = Objects.requireNonNull(uri); + this.mediaType = mediaType; + + if (width != null && width < 0) { + throw new IllegalArgumentException("Width cannot be negative."); + } + this.width = width; + + if (height != null && height < 0) { + throw new IllegalArgumentException("Height cannot be negative."); + } + this.height = height; + } + + public String getUri() { + return uri; + } + + public String getMediaType() { + return mediaType; + } + + public Integer getWidth() { + return width; + } + + public Integer getHeight() { + return height; + } + + @Override + public CharSequence toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder sb = new XmlStringBuilder(this, xmlEnvironment); + return sb.attribute(ELEM_URI, uri) + .optAttribute(ELEM_MEDIA_TYPE, mediaType) + .optAttribute(ELEM_WIDTH, width) + .optAttribute(ELEM_HEIGHT, height) + .closeEmptyElement(); + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/package-info.java new file mode 100644 index 000000000..a13671247 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/element/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2023 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Smacks implementation of XEP-0264: Jingle Content Thumbnails. + */ +package org.jivesoftware.smackx.thumbnails.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProvider.java new file mode 100644 index 000000000..b21f7e8ea --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProvider.java @@ -0,0 +1,45 @@ +/** + * + * Copyright 2023 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.thumbnails.provider; + +import java.io.IOException; +import java.text.ParseException; + +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.thumbnails.element.ThumbnailElement; + +public class ThumbnailElementProvider extends ExtensionElementProvider { + @Override + public ThumbnailElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws XmlPullParserException, IOException, SmackParsingException, ParseException { + String uri = parser.getAttributeValue(ThumbnailElement.ELEM_URI); + String mediaType = parser.getAttributeValue(ThumbnailElement.ELEM_MEDIA_TYPE); + String width = parser.getAttributeValue(ThumbnailElement.ELEM_WIDTH); + String height = parser.getAttributeValue(ThumbnailElement.ELEM_HEIGHT); + + return new ThumbnailElement( + uri, + mediaType, + width == null ? null : Integer.parseInt(width), + height == null ? null : Integer.parseInt(height) + ); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/package-info.java new file mode 100644 index 000000000..20816c2ce --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/thumbnails/provider/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2023 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Smacks implementation of XEP-0264: Jingle Content Thumbnails. + */ +package org.jivesoftware.smackx.thumbnails.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index eb0c1a45f..822bc5837 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -2,6 +2,13 @@ + + + thumbnail + urn:xmpp:thumbs:1 + org.jivesoftware.smackx.thumbnails.provider.ThumbnailElementProvider + + sent @@ -345,6 +352,13 @@ org.jivesoftware.smackx.fallback_indication.provider.FallbackIndicationElementProvider + + + file + urn:xmpp:file:metadata:0 + org.jivesoftware.smackx.file_metadata.provider.FileMetadataElementProvider + + query diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/file_metadata/FileMetadataElementTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/file_metadata/FileMetadataElementTest.java new file mode 100644 index 000000000..c016bfced --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/file_metadata/FileMetadataElementTest.java @@ -0,0 +1,167 @@ +/** + * + * Copyright 2020 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.file_metadata; + +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.text.ParseException; +import java.util.Date; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.SmackTestUtil; +import org.jivesoftware.smackx.file_metadata.element.FileMetadataElement; +import org.jivesoftware.smackx.file_metadata.provider.FileMetadataElementProvider; +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.jxmpp.util.XmppDateTime; + +public class FileMetadataElementTest extends SmackTestSuite { + + private static Date date; + private static FileMetadataElement metadataElement; + private static final String expectedXml = "" + + "2015-07-26T20:46:00.000+00:00" + + "1920" + + "1080" + + "Picture of 24th XSF Summit" + + "Foto vom 24. XSF Summit" + + "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=" + + "63000" + + "text/plain" + + "text.txt" + + "6144" + + ""; + + private static final String expectedLegacyXml = "" + + "2015-07-26T20:46:00.000+00:00" + + "1920x1080" + + "Picture of 24th XSF Summit" + + "Foto vom 24. XSF Summit" + + "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=" + + "63000" + + "text/plain" + + "text.txt" + + "6144" + + ""; + + @BeforeAll + public static void setup() throws ParseException { + date = XmppDateTime.parseDate("2015-07-26T21:46:00+01:00"); + metadataElement = FileMetadataElement.builder() + .setModificationDate(date) + .setWidth(1920) + .setHeight(1080) + .addDescription("Picture of 24th XSF Summit") + .addDescription("Foto vom 24. XSF Summit", "de") + .addHash(new HashElement(HashManager.ALGORITHM.SHA_256, "2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=")) + .setLength(63000) + .setMediaType("text/plain") + .setName("text.txt") + .setSize(6144) + .build(); + } + + + @Test + public void testSerialization() { + assertXmlSimilar(expectedXml, metadataElement.toXML().toString()); + } + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParsing(SmackTestUtil.XmlPullParserKind parserKind) throws Exception { + FileMetadataElement parsed = SmackTestUtil.parse(expectedXml, FileMetadataElementProvider.class, parserKind); + + assertEquals(metadataElement, parsed); + } + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testLegacyParsing(SmackTestUtil.XmlPullParserKind parserKind) throws Exception { + FileMetadataElement parsed = SmackTestUtil.parse(expectedLegacyXml, FileMetadataElementProvider.class, parserKind); + + assertEquals(metadataElement, parsed); + } + + @Test + public void nameIsEscaped() { + FileMetadataElement e = FileMetadataElement.builder().setName("/etc/passwd").build(); + assertEquals("%2Fetc%2Fpasswd", e.getName()); + } + + @Test + public void rejectNegativeSize() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setSize(-1)); + } + + @Test + public void rejectNegativeLength() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setLength(-1)); + } + + @Test + public void rejectNegativeWidth() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setWidth(-1)); + } + + @Test + public void rejectNegativeHeight() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setHeight(-1)); + } + + @Test + public void rejectEmptyDescription() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().addDescription("")); + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().addDescription(null)); + } + + @Test + public void rejectEmptyNameElement() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setName("")); + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setName(null)); + } + + @Test + public void rejectEmptyMediaTypeElement() { + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setMediaType("")); + assertThrows(IllegalArgumentException.class, () -> FileMetadataElement.builder().setMediaType(null)); + } + + @Test + public void getDescTest() { + FileMetadataElement metadataElement = FileMetadataElement.builder() + .addDescription("Foo", "br") + .addDescription("Baz") + .addDescription("Bag", "en") + .build(); + + assertEquals("Foo", metadataElement.getDescription("br")); + assertEquals("Baz", metadataElement.getDescription(null)); + assertEquals("Baz", metadataElement.getDescription()); + assertEquals("Bag", metadataElement.getDescription("en")); + assertNull(metadataElement.getDescription("null")); + assertEquals(3, metadataElement.getDescriptions().size()); + } +} diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElementTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElementTest.java new file mode 100644 index 000000000..87d03bbb6 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/element/ThumbnailElementTest.java @@ -0,0 +1,56 @@ +/** + * + * Copyright 2023 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.thumbnails.element; + +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ThumbnailElementTest { + + @Test + public void uriIsRequired() { + assertThrows(IllegalArgumentException.class, () -> new ThumbnailElement(null)); + assertThrows(IllegalArgumentException.class, () -> new ThumbnailElement(null, "image/png", 128, 128)); + } + + @Test + public void testMinimal() { + ThumbnailElement minimal = new ThumbnailElement("cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org"); + + assertXmlSimilar("", + minimal.toXML()); + } + + @Test + public void testFull() { + ThumbnailElement full = new ThumbnailElement( + "cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org", + "image/png", + 128, + 96); + + assertXmlSimilar("", + full.toXML()); + } +} diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProviderTest.java new file mode 100644 index 000000000..dbaec7c9a --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/thumbnails/provider/ThumbnailElementProviderTest.java @@ -0,0 +1,64 @@ +/** + * + * Copyright 2023 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.thumbnails.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; + +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.test.util.SmackTestUtil; +import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.thumbnails.element.ThumbnailElement; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class ThumbnailElementProviderTest { + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParseFull(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { + String xml = ""; + + ThumbnailElement element = SmackTestUtil.parse(xml, ThumbnailElementProvider.class, parserKind); + + assertEquals("cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org", element.getUri()); + assertEquals("image/png", element.getMediaType()); + assertEquals(128, element.getWidth()); + assertEquals(96, element.getHeight()); + } + + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParseMinimal(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { + String xml = ""; + + ThumbnailElement element = SmackTestUtil.parse(xml, ThumbnailElementProvider.class, parserKind); + + assertEquals("cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org", element.getUri()); + assertNull(element.getMediaType()); + assertNull(element.getWidth()); + assertNull(element.getHeight()); + } +}