diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryBean.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryBean.java
index bbc0eab7ea..949fbafa09 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryBean.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryBean.java
@@ -68,6 +68,8 @@ public class EntryBean {
private boolean rightToLeft = false;
private boolean pinnedToMain = false;
private String enclosureURL = null;
+ private String enclosureType = null;
+ private String enclosureLength = null;
private String searchDescription = null;
private int commentCount = 0;
@@ -227,6 +229,22 @@ public String getEnclosureURL() {
public void setEnclosureURL(String enclosureUrl) {
this.enclosureURL = enclosureUrl;
}
+
+ public String getEnclosureType() {
+ return enclosureType;
+ }
+
+ public void setEnclosureType(String enclosureType) {
+ this.enclosureType = enclosureType;
+ }
+
+ public String getEnclosureLength() {
+ return enclosureLength;
+ }
+
+ public void setEnclosureLength(String enclosureLength) {
+ this.enclosureLength = enclosureLength;
+ }
public String getSearchDescription() {
return searchDescription;
@@ -390,6 +408,10 @@ public void copyFrom(WeblogEntry entry, Locale locale) {
for (WeblogEntryAttribute attr : attrs) {
if ("att_mediacast_url".equals(attr.getName())) {
setEnclosureURL(attr.getValue());
+ } else if ("att_mediacast_type".equals(attr.getName())) {
+ setEnclosureType(attr.getValue());
+ } else if ("att_mediacast_length".equals(attr.getName())) {
+ setEnclosureLength(attr.getValue());
}
}
}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java
index a12dc18fff..aee5b10bd1 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java
@@ -48,15 +48,13 @@
import org.apache.roller.weblogger.ui.core.plugins.UIPluginManager;
import org.apache.roller.weblogger.ui.core.plugins.WeblogEntryEditor;
import org.apache.roller.weblogger.ui.struts2.util.UIAction;
-import org.apache.roller.weblogger.util.cache.CacheManager;
+import org.apache.roller.weblogger.util.EnclosureMetadata;
import org.apache.roller.weblogger.util.MailUtil;
-import org.apache.roller.weblogger.util.MediacastException;
-import org.apache.roller.weblogger.util.MediacastResource;
-import org.apache.roller.weblogger.util.MediacastUtil;
import org.apache.roller.weblogger.util.RollerMessages;
import org.apache.roller.weblogger.util.RollerMessages.RollerMessage;
import org.apache.roller.weblogger.util.Trackback;
import org.apache.roller.weblogger.util.TrackbackNotAllowedException;
+import org.apache.roller.weblogger.util.cache.CacheManager;
import org.apache.struts2.convention.annotation.AllowedMethods;
import org.apache.struts2.interceptor.validation.SkipValidation;
@@ -192,6 +190,19 @@ public String publish() {
*/
private String save() {
if (!hasActionErrors()) {
+ EnclosureMetadata enclosure = null;
+ if (!StringUtils.isEmpty(getBean().getEnclosureURL())) {
+ try {
+ enclosure = EnclosureMetadata.of(
+ getBean().getEnclosureURL(),
+ getBean().getEnclosureType(),
+ getBean().getEnclosureLength());
+ } catch (IllegalArgumentException e) {
+ addError("weblogEdit.enclosureMetadataInvalid");
+ return INPUT;
+ }
+ }
+
try {
WeblogEntryManager weblogEntryManager = WebloggerFactory.getWeblogger()
.getWeblogEntryManager();
@@ -223,24 +234,13 @@ private String save() {
weblogEntry.setPinnedToMain(getBean().getPinnedToMain());
}
- if (!StringUtils.isEmpty(getBean().getEnclosureURL())) {
- try {
- // Fetch MediaCast resource
- log.debug("Checking MediaCast attributes");
- MediacastResource mediacast = MediacastUtil
- .lookupResource(getBean().getEnclosureURL());
-
- // set mediacast attributes
- weblogEntry.putEntryAttribute("att_mediacast_url",
- mediacast.getUrl());
- weblogEntry.putEntryAttribute("att_mediacast_type",
- mediacast.getContentType());
- weblogEntry.putEntryAttribute("att_mediacast_length", ""
- + mediacast.getLength());
-
- } catch (MediacastException ex) {
- addMessage(getText(ex.getErrorKey()));
- }
+ if (enclosure != null) {
+ weblogEntry.putEntryAttribute("att_mediacast_url",
+ enclosure.getUrl());
+ weblogEntry.putEntryAttribute("att_mediacast_type",
+ enclosure.getContentType());
+ weblogEntry.putEntryAttribute("att_mediacast_length",
+ enclosure.getLength());
} else if ("entryEdit".equals(actionName)) {
try {
// if MediaCast string is empty, clean out MediaCast
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/EnclosureMetadata.java b/app/src/main/java/org/apache/roller/weblogger/util/EnclosureMetadata.java
new file mode 100644
index 0000000000..43ac43e0ed
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/util/EnclosureMetadata.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.roller.weblogger.util;
+
+import java.util.regex.Pattern;
+import org.apache.commons.validator.routines.UrlValidator;
+
+/**
+ * Validated metadata for an RSS or Atom enclosure.
+ */
+public final class EnclosureMetadata {
+
+ private static final UrlValidator URL_VALIDATOR = new UrlValidator(
+ new String[] {"http", "https"}, UrlValidator.ALLOW_LOCAL_URLS);
+
+ private static final Pattern MEDIA_TYPE = Pattern.compile(
+ "[!#$%&'*+.^_`|~0-9A-Za-z-]+/[!#$%&'*+.^_`|~0-9A-Za-z-]+");
+
+ private final String url;
+ private final String contentType;
+ private final String length;
+
+ private EnclosureMetadata(String url, String contentType, String length) {
+ this.url = url;
+ this.contentType = contentType;
+ this.length = length;
+ }
+
+ public static EnclosureMetadata of(String url, String contentType, String length) {
+ String normalizedUrl = normalize(url);
+ String normalizedType = normalize(contentType);
+ String normalizedLength = normalize(length);
+
+ if (!URL_VALIDATOR.isValid(normalizedUrl)) {
+ throw new IllegalArgumentException("Enclosure URL must be an absolute HTTP or HTTPS URL");
+ }
+ if (!MEDIA_TYPE.matcher(normalizedType).matches()) {
+ throw new IllegalArgumentException("Enclosure type must be a valid media type");
+ }
+
+ final long byteLength;
+ try {
+ byteLength = Long.parseLong(normalizedLength);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Enclosure length must be a non-negative integer", e);
+ }
+ if (byteLength < 0) {
+ throw new IllegalArgumentException("Enclosure length must be a non-negative integer");
+ }
+
+ return new EnclosureMetadata(
+ normalizedUrl, normalizedType, Long.toString(byteLength));
+ }
+
+ private static String normalize(String value) {
+ return value == null ? "" : value.trim();
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public String getLength() {
+ return length;
+ }
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/MediacastException.java b/app/src/main/java/org/apache/roller/weblogger/util/MediacastException.java
deleted file mode 100644
index b9621ccb69..0000000000
--- a/app/src/main/java/org/apache/roller/weblogger/util/MediacastException.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. The ASF licenses this file to You
- * 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. For additional information regarding
- * copyright in this work, please see the NOTICE file in the top level
- * directory of this distribution.
- */
-
-package org.apache.roller.weblogger.util;
-
-import org.apache.roller.weblogger.WebloggerException;
-
-
-/**
- * An exception thrown when dealing with Mediacast files.
- */
-public class MediacastException extends WebloggerException {
-
- private int errorCode = 0;
- private String errorKey = null;
-
-
- public MediacastException(int code, String msgKey) {
- this.errorCode = code;
- this.errorKey = msgKey;
- }
-
-
- public MediacastException(int code, String msgKey, Throwable t) {
- super(t);
- this.errorCode = code;
- this.errorKey = msgKey;
- }
-
-
- public int getErrorCode() {
- return errorCode;
- }
-
- public String getErrorKey() {
- return errorKey;
- }
-
- public void setErrorCode(int errorCode) {
- this.errorCode = errorCode;
- }
-
- public void setErrorKey(String errorKey) {
- this.errorKey = errorKey;
- }
-
-}
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/MediacastResource.java b/app/src/main/java/org/apache/roller/weblogger/util/MediacastResource.java
deleted file mode 100644
index 6b649dfaa7..0000000000
--- a/app/src/main/java/org/apache/roller/weblogger/util/MediacastResource.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. The ASF licenses this file to You
- * 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. For additional information regarding
- * copyright in this work, please see the NOTICE file in the top level
- * directory of this distribution.
- */
-
-package org.apache.roller.weblogger.util;
-
-
-/**
- * An external 'mediacast' resource, typically a podcast, video, etc.
- *
- * This class is mainly used by weblog entries to track external resources used
- * in postings via enclosures.
- */
-public class MediacastResource {
-
- private String url = null;
- private String contentType = null;
- private long length = 0;
-
-
- public MediacastResource(String u, String c, long l) {
- this.setUrl(u);
- this.setContentType(c);
- this.setLength(l);
- }
-
-
- public String getUrl() {
- return url;
- }
-
- public void setUrl(String url) {
- this.url = url;
- }
-
- public String getContentType() {
- return contentType;
- }
-
- public void setContentType(String contentType) {
- this.contentType = contentType;
- }
-
- public long getLength() {
- return length;
- }
-
- public void setLength(long length) {
- this.length = length;
- }
-
-
- @Override
- public String toString() {
- StringBuilder buf = new StringBuilder();
-
- buf.append("url = ").append(getUrl()).append("\n");
- buf.append("contentType = ").append(getContentType()).append("\n");
- buf.append("length = ").append(getLength()).append("\n");
-
- return buf.toString();
- }
-
-}
diff --git a/app/src/main/java/org/apache/roller/weblogger/util/MediacastUtil.java b/app/src/main/java/org/apache/roller/weblogger/util/MediacastUtil.java
deleted file mode 100644
index 35df6e41e4..0000000000
--- a/app/src/main/java/org/apache/roller/weblogger/util/MediacastUtil.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. The ASF licenses this file to You
- * 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. For additional information regarding
- * copyright in this work, please see the NOTICE file in the top level
- * directory of this distribution.
- */
-
-package org.apache.roller.weblogger.util;
-
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-
-/**
- * Utility for deailing with mediacast files.
- */
-public final class MediacastUtil {
-
- private static final Log LOG = LogFactory.getLog(MediacastUtil.class);
-
- public static final int BAD_URL = 1;
- public static final int CHECK_FAILED = 2;
- public static final int BAD_RESPONSE = 3;
- public static final int INCOMPLETE = 4;
-
-
- // non-instantiable
- private MediacastUtil() {}
-
-
- /**
- * Validate a Mediacast resource.
- */
- public static MediacastResource lookupResource(String url)
- throws MediacastException {
-
- if(url == null || url.isBlank()) {
- return null;
- }
-
- MediacastResource resource = null;
- try {
- HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
- con.setRequestMethod("HEAD");
- int response = con.getResponseCode();
- String message = con.getResponseMessage();
-
- if(response != 200) {
- LOG.debug("Mediacast error " + response + ":" + message + " from url " + url);
- throw new MediacastException(BAD_RESPONSE, "weblogEdit.mediaCastResponseError");
- } else {
- String contentType = con.getContentType();
- long length = con.getContentLength();
-
- if(contentType == null || length == -1) {
- LOG.debug("Response valid, but contentType or length is invalid");
- throw new MediacastException(INCOMPLETE, "weblogEdit.mediaCastLacksContentTypeOrLength");
- }
-
- resource = new MediacastResource(url, contentType, length);
- LOG.debug("Valid mediacast resource = " + resource.toString());
-
- }
- } catch (MalformedURLException mfue) {
- LOG.debug("Malformed MediaCast url: " + url);
- throw new MediacastException(BAD_URL, "weblogEdit.mediaCastUrlMalformed", mfue);
- } catch (Exception e) {
- LOG.error("ERROR while checking MediaCast URL: " + url + ": " + e.getMessage());
- throw new MediacastException(CHECK_FAILED, "weblogEdit.mediaCastFailedFetchingInfo", e);
- }
- return resource;
- }
-
-}
diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties
index 66072c23f0..cc0b9679fb 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -1606,13 +1606,12 @@ weblogEdit.trackbackError404=Trackback failed, could not reach trackback URL. A
weblogEdit.hasComments=Comments [{1}]
weblogEdit.enclosureURL=Enclosure URL
-weblogEdit.enclosureURL.tooltip=Podcast or other multimedia URL to embed within the RSS & Atom feeds for this blog entry.
-weblogEdit.enclosureType=Type
-weblogEdit.enclosureLength=Length
-weblogEdit.mediaCastFailedFetchingInfo=Unable to reach the enclosure. Check the hostname in the URL.
-weblogEdit.mediaCastUrlMalformed=The enclosure URL was malformed.
-weblogEdit.mediaCastResponseError=The enclosure server returned an error. Do you have the right URL?
-weblogEdit.mediaCastLacksContentTypeOrLength=Unable to use enclosure URL. Server provided no content type or no length.
+weblogEdit.enclosureURL.tooltip=Absolute HTTP or HTTPS URL to embed within the RSS & Atom feeds for this blog entry.
+weblogEdit.enclosureType=Enclosure media type
+weblogEdit.enclosureType.tooltip=Media type supplied for the enclosure, for example audio/mpeg.
+weblogEdit.enclosureLength=Enclosure length
+weblogEdit.enclosureLength.tooltip=Non-negative enclosure size in bytes.
+weblogEdit.enclosureMetadataInvalid=Enter an absolute HTTP or HTTPS enclosure URL, a valid media type, and a non-negative byte length.
weblogEdit.mediaCastErrorRemoving=Error removing MediaCast from weblog entry
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp
index 61995356ad..d7c194de28 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp
@@ -238,15 +238,10 @@
-
-
-
- :
-
- :
-
-
-
+
+
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/MediaFileAddSuccess.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/MediaFileAddSuccess.jsp
index c3cbae0f22..3f9aa71e02 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/MediaFileAddSuccess.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/MediaFileAddSuccess.jsp
@@ -25,6 +25,8 @@
+
+
@@ -96,7 +98,9 @@
')"/>
+ onchange="setEnclosure('',
+ '',
+ '')"/>
@@ -132,7 +136,7 @@
-
+
@@ -201,8 +205,10 @@
return false;
}
- function setEnclosure(url) {
+ function setEnclosure(url, type, length) {
$("#enclosureURL").get(0).value = url;
+ $("#enclosureType").get(0).value = type;
+ $("#enclosureLength").get(0).value = length;
if (isImageChecked()) {
$("#submit").attr("disabled", false);
return;
diff --git a/app/src/test/java/org/apache/roller/weblogger/util/EnclosureMetadataTest.java b/app/src/test/java/org/apache/roller/weblogger/util/EnclosureMetadataTest.java
new file mode 100644
index 0000000000..24a09a3505
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/util/EnclosureMetadataTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.roller.weblogger.util;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class EnclosureMetadataTest {
+
+ @Test
+ void acceptsHttpMetadataWithoutOpeningTheResource() {
+ EnclosureMetadata metadata = EnclosureMetadata.of(
+ "http://127.0.0.1:9/audio.ogg", "audio/ogg", "1234");
+
+ assertEquals("http://127.0.0.1:9/audio.ogg", metadata.getUrl());
+ assertEquals("audio/ogg", metadata.getContentType());
+ assertEquals("1234", metadata.getLength());
+ }
+
+ @Test
+ void acceptsHttpsAndTrimsMetadata() {
+ EnclosureMetadata metadata = EnclosureMetadata.of(
+ " https://media.example.org/show.mp3 ",
+ " audio/mpeg ", " 3141592654 ");
+
+ assertEquals("https://media.example.org/show.mp3", metadata.getUrl());
+ assertEquals("audio/mpeg", metadata.getContentType());
+ assertEquals("3141592654", metadata.getLength());
+ }
+
+ @Test
+ void rejectsUnsupportedOrIncompleteMetadata() {
+ assertThrows(IllegalArgumentException.class,
+ () -> EnclosureMetadata.of("file:///tmp/audio.ogg", "audio/ogg", "12"));
+ assertThrows(IllegalArgumentException.class,
+ () -> EnclosureMetadata.of("https://example.org/audio", "not-a-type", "12"));
+ assertThrows(IllegalArgumentException.class,
+ () -> EnclosureMetadata.of("https://example.org/audio", "audio/ogg", "-1"));
+ assertThrows(IllegalArgumentException.class,
+ () -> EnclosureMetadata.of("https://example.org/audio", "audio/ogg", "unknown"));
+ }
+}