mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-25 15:52:06 +01:00
Fix and improve the HTTP File Upload implementation
Fix a few resource leaks. Improve the API and add an integration test. Also add compability layer for XEP-0363: HTTP File Upload 0.2. SMACK-747
This commit is contained in:
parent
72d4c8b611
commit
09b6608a3a
26 changed files with 691 additions and 177 deletions
|
@ -235,4 +235,19 @@ public class ParserUtils {
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getRequiredAttribute(XmlPullParser parser, String name) throws IOException {
|
||||||
|
String value = parser.getAttributeValue("", name);
|
||||||
|
if (StringUtils.isNullOrEmpty(value)) {
|
||||||
|
throw new IOException("Attribute " + name + " is null or empty (" + value + ')');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRequiredNextText(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
|
String text = parser.nextText();
|
||||||
|
if (StringUtils.isNullOrEmpty(text)) {
|
||||||
|
throw new IOException("Next text is null or empty (" + text + ')');
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.jivesoftware.smackx.httpfileupload;
|
package org.jivesoftware.smackx.httpfileupload;
|
||||||
|
|
||||||
import org.jivesoftware.smack.AbstractConnectionListener;
|
import org.jivesoftware.smack.AbstractConnectionListener;
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.ConnectionCreationListener;
|
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||||
import org.jivesoftware.smack.Manager;
|
import org.jivesoftware.smack.Manager;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
@ -25,8 +26,10 @@ import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.UploadService.Version;
|
||||||
import org.jivesoftware.smackx.httpfileupload.element.Slot;
|
import org.jivesoftware.smackx.httpfileupload.element.Slot;
|
||||||
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
|
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest_V0_2;
|
||||||
import org.jivesoftware.smackx.xdata.FormField;
|
import org.jivesoftware.smackx.xdata.FormField;
|
||||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
@ -34,25 +37,34 @@ import org.jxmpp.jid.DomainBareJid;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP File Upload manager class.
|
* A manager for XEP-0363: HTTP File Upload.
|
||||||
* XEP version 0.2.5
|
|
||||||
*
|
*
|
||||||
* @author Grigory Fedorov
|
* @author Grigory Fedorov
|
||||||
|
* @author Florian Schmaus
|
||||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||||
*/
|
*/
|
||||||
public final class HttpFileUploadManager extends Manager {
|
public final class HttpFileUploadManager extends Manager {
|
||||||
|
|
||||||
|
public static final String NAMESPACE = "urn:xmpp:http:upload:0";
|
||||||
|
public static final String NAMESPACE_0_2 = "urn:xmpp:http:upload";
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName());
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -65,8 +77,10 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<XMPPConnection, HttpFileUploadManager> INSTANCES = new WeakHashMap<>();
|
private static final Map<XMPPConnection, HttpFileUploadManager> INSTANCES = new WeakHashMap<>();
|
||||||
private DomainBareJid defaultUploadService;
|
|
||||||
private Long maxFileSize;
|
private UploadService defaultUploadService;
|
||||||
|
|
||||||
|
private SSLSocketFactory tlsSocketFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain the HttpFileUploadManager responsible for a connection.
|
* Obtain the HttpFileUploadManager responsible for a connection.
|
||||||
|
@ -106,6 +120,40 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UploadService uploadServiceFrom(DiscoverInfo discoverInfo) {
|
||||||
|
assert(containsHttpFileUploadNamespace(discoverInfo));
|
||||||
|
|
||||||
|
UploadService.Version version;
|
||||||
|
if (discoverInfo.containsFeature(NAMESPACE)) {
|
||||||
|
version = Version.v0_3;
|
||||||
|
} else if (discoverInfo.containsFeature(NAMESPACE_0_2)) {
|
||||||
|
version = Version.v0_2;
|
||||||
|
} else {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainBareJid address = discoverInfo.getFrom().asDomainBareJid();
|
||||||
|
|
||||||
|
DataForm dataForm = DataForm.from(discoverInfo);
|
||||||
|
if (dataForm == null) {
|
||||||
|
return new UploadService(address, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
FormField field = dataForm.getField("max-file-size");
|
||||||
|
if (field == null) {
|
||||||
|
return new UploadService(address, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> values = field.getValues();
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return new UploadService(address, version);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Long maxFileSize = Long.valueOf(values.get(0));
|
||||||
|
return new UploadService(address, version, maxFileSize);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discover upload service.
|
* Discover upload service.
|
||||||
*
|
*
|
||||||
|
@ -122,39 +170,20 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
*/
|
*/
|
||||||
public boolean discoverUploadService() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
public boolean discoverUploadService() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
InterruptedException, SmackException.NoResponseException {
|
InterruptedException, SmackException.NoResponseException {
|
||||||
defaultUploadService = null;
|
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||||
maxFileSize = null;
|
List<DiscoverInfo> servicesDiscoverInfo = sdm
|
||||||
|
.findServicesDiscoverInfo(NAMESPACE, true, true);
|
||||||
List<DiscoverInfo> servicesDiscoverInfo = ServiceDiscoveryManager.getInstanceFor(connection())
|
|
||||||
.findServicesDiscoverInfo(SlotRequest.NAMESPACE, true, false);
|
|
||||||
|
|
||||||
if (servicesDiscoverInfo.isEmpty()) {
|
if (servicesDiscoverInfo.isEmpty()) {
|
||||||
return false;
|
servicesDiscoverInfo = sdm.findServicesDiscoverInfo(NAMESPACE_0_2, true, true);
|
||||||
|
if (servicesDiscoverInfo.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DiscoverInfo discoverInfo = servicesDiscoverInfo.get(0);
|
DiscoverInfo discoverInfo = servicesDiscoverInfo.get(0);
|
||||||
|
|
||||||
defaultUploadService = discoverInfo.getFrom().asDomainBareJid();
|
defaultUploadService = uploadServiceFrom(discoverInfo);
|
||||||
|
|
||||||
if (defaultUploadService == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataForm dataForm = DataForm.from(discoverInfo);
|
|
||||||
if (dataForm == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormField field = dataForm.getField("max-file-size");
|
|
||||||
if (field == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> values = field.getValues();
|
|
||||||
if (!values.isEmpty()) {
|
|
||||||
maxFileSize = Long.valueOf(values.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,19 +201,10 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
*
|
*
|
||||||
* @return upload service JID or null if not available
|
* @return upload service JID or null if not available
|
||||||
*/
|
*/
|
||||||
public DomainBareJid getDefaultUploadService() {
|
public UploadService getDefaultUploadService() {
|
||||||
return defaultUploadService;
|
return defaultUploadService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get max file size allowed by upload service.
|
|
||||||
*
|
|
||||||
* @return max file size in bytes or null if not available
|
|
||||||
*/
|
|
||||||
public Long getMaxFileSize() {
|
|
||||||
return maxFileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request slot and uploaded file to HTTP file upload service.
|
* Request slot and uploaded file to HTTP file upload service.
|
||||||
*
|
*
|
||||||
|
@ -203,19 +223,6 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
return uploadFile(file, null);
|
return uploadFile(file, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback interface to get upload progress.
|
|
||||||
*/
|
|
||||||
public interface UploadProgressListener {
|
|
||||||
/**
|
|
||||||
* Callback for displaying upload progress.
|
|
||||||
*
|
|
||||||
* @param uploadedBytes - number of bytes uploaded at the moment
|
|
||||||
* @param totalBytes - total number of bytes to be uploaded
|
|
||||||
*/
|
|
||||||
void onUploadProgress(long uploadedBytes, long totalBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request slot and uploaded file to HTTP file upload service with progress callback.
|
* Request slot and uploaded file to HTTP file upload service with progress callback.
|
||||||
*
|
*
|
||||||
|
@ -233,27 +240,26 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
*/
|
*/
|
||||||
public URL uploadFile(File file, UploadProgressListener listener) throws InterruptedException,
|
public URL uploadFile(File file, UploadProgressListener listener) throws InterruptedException,
|
||||||
XMPPException.XMPPErrorException, SmackException, IOException {
|
XMPPException.XMPPErrorException, SmackException, IOException {
|
||||||
|
if (!file.isFile()) {
|
||||||
|
throw new FileNotFoundException("The path " + file.getAbsolutePath() + " is not a file");
|
||||||
|
}
|
||||||
final Slot slot = requestSlot(file.getName(), file.length(), "application/octet-stream");
|
final Slot slot = requestSlot(file.getName(), file.length(), "application/octet-stream");
|
||||||
|
|
||||||
uploadFile(file, slot.getPutUrl(), listener);
|
uploadFile(file, slot, listener);
|
||||||
|
|
||||||
return slot.getGetUrl();
|
return slot.getGetUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a new upload slot from default upload service (if discovered).
|
* Request a new upload slot from default upload service (if discovered). When you get slot you should upload file
|
||||||
*
|
* to PUT URL and share GET URL. Note that this is a synchronous call -- Smack must wait for the server response.
|
||||||
* When you get slot you should upload file to PUT URL and share GET URL.
|
|
||||||
* Note that this is a synchronous call -- Smack must wait for the server response.
|
|
||||||
*
|
*
|
||||||
* @param filename name of file to be uploaded
|
* @param filename name of file to be uploaded
|
||||||
* @param fileSize file size in bytes -- must be less or equal
|
* @param fileSize file size in bytes.
|
||||||
* to {@link HttpFileUploadManager#getMaxFileSize()} (if available)
|
|
||||||
* @return file upload Slot in case of success
|
* @return file upload Slot in case of success
|
||||||
|
* @throws IllegalArgumentException if fileSize is less than or equal to zero or greater than the maximum size
|
||||||
* @throws IllegalArgumentException if fileSize is less than or equal to zero
|
* supported by the service.
|
||||||
* or greater than {@link HttpFileUploadManager#getMaxFileSize()}
|
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @throws XMPPException.XMPPErrorException
|
* @throws XMPPException.XMPPErrorException
|
||||||
* @throws SmackException.NotConnectedException
|
* @throws SmackException.NotConnectedException
|
||||||
|
@ -271,13 +277,12 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
* Note that this is a synchronous call -- Smack must wait for the server response.
|
* Note that this is a synchronous call -- Smack must wait for the server response.
|
||||||
*
|
*
|
||||||
* @param filename name of file to be uploaded
|
* @param filename name of file to be uploaded
|
||||||
* @param fileSize file size in bytes -- must be less or equal
|
* @param fileSize file size in bytes.
|
||||||
* to {@link HttpFileUploadManager#getMaxFileSize()} (if available)
|
|
||||||
* @param contentType file content-type or null
|
* @param contentType file content-type or null
|
||||||
* @return file upload Slot in case of success
|
* @return file upload Slot in case of success
|
||||||
|
|
||||||
* @throws IllegalArgumentException if fileSize is less than or equal to zero
|
* @throws IllegalArgumentException if fileSize is less than or equal to zero or greater than the maximum size
|
||||||
* or greater than {@link HttpFileUploadManager#getMaxFileSize()}
|
* supported by the service.
|
||||||
* @throws SmackException.NotConnectedException
|
* @throws SmackException.NotConnectedException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @throws XMPPException.XMPPErrorException
|
* @throws XMPPException.XMPPErrorException
|
||||||
|
@ -295,81 +300,164 @@ public final class HttpFileUploadManager extends Manager {
|
||||||
* Note that this is a synchronous call -- Smack must wait for the server response.
|
* Note that this is a synchronous call -- Smack must wait for the server response.
|
||||||
*
|
*
|
||||||
* @param filename name of file to be uploaded
|
* @param filename name of file to be uploaded
|
||||||
* @param fileSize file size in bytes -- must be less or equal
|
* @param fileSize file size in bytes.
|
||||||
* to {@link HttpFileUploadManager#getMaxFileSize()} (if available)
|
|
||||||
* @param contentType file content-type or null
|
* @param contentType file content-type or null
|
||||||
* @param uploadService upload service to use or null for default one
|
* @param uploadServiceAddress the address of the upload service to use or null for default one
|
||||||
* @return file upload Slot in case of success
|
* @return file upload Slot in case of success
|
||||||
* @throws IllegalArgumentException if fileSize is less than or equal to zero
|
* @throws IllegalArgumentException if fileSize is less than or equal to zero or greater than the maximum size
|
||||||
* or greater than {@link HttpFileUploadManager#getMaxFileSize()}
|
* supported by the service.
|
||||||
* @throws SmackException
|
* @throws SmackException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @throws XMPPException.XMPPErrorException
|
* @throws XMPPException.XMPPErrorException
|
||||||
*/
|
*/
|
||||||
public Slot requestSlot(String filename, long fileSize, String contentType, DomainBareJid uploadService)
|
public Slot requestSlot(String filename, long fileSize, String contentType, DomainBareJid uploadServiceAddress)
|
||||||
throws SmackException, InterruptedException, XMPPException.XMPPErrorException {
|
throws SmackException, InterruptedException, XMPPException.XMPPErrorException {
|
||||||
if (defaultUploadService == null && uploadService == null) {
|
final XMPPConnection connection = connection();
|
||||||
throw new SmackException("No upload service specified or discovered.");
|
final UploadService defaultUploadService = this.defaultUploadService;
|
||||||
}
|
|
||||||
|
|
||||||
if (uploadService == null && maxFileSize != null) {
|
// The upload service we are going to use.
|
||||||
if (fileSize > maxFileSize) {
|
UploadService uploadService;
|
||||||
throw new IllegalArgumentException("Requested file size " + fileSize
|
|
||||||
+ " is greater than max allowed size " + maxFileSize);
|
if (uploadServiceAddress == null) {
|
||||||
|
uploadService = defaultUploadService;
|
||||||
|
} else {
|
||||||
|
if (defaultUploadService != null && defaultUploadService.getAddress().equals(uploadServiceAddress)) {
|
||||||
|
// Avoid performing a service discovery if we already know about the given service.
|
||||||
|
uploadService = defaultUploadService;
|
||||||
|
} else {
|
||||||
|
DiscoverInfo discoverInfo = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(uploadServiceAddress);
|
||||||
|
if (!containsHttpFileUploadNamespace(discoverInfo)) {
|
||||||
|
throw new IllegalArgumentException("There is no HTTP upload service running at the given address '"
|
||||||
|
+ uploadServiceAddress + '\'');
|
||||||
|
}
|
||||||
|
uploadService = uploadServiceFrom(discoverInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SlotRequest slotRequest = new SlotRequest(filename, fileSize, contentType);
|
if (uploadService == null) {
|
||||||
if (uploadService != null) {
|
throw new SmackException("No upload service specified and also none discovered.");
|
||||||
slotRequest.setTo(uploadService);
|
|
||||||
} else {
|
|
||||||
slotRequest.setTo(defaultUploadService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection().createStanzaCollectorAndSend(slotRequest).nextResultOrThrow();
|
if (!uploadService.acceptsFileOfSize(fileSize)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Requested file size " + fileSize + " is greater than max allowed size " + uploadService.getMaxFileSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
SlotRequest slotRequest;
|
||||||
|
switch (uploadService.getVersion()) {
|
||||||
|
case v0_3:
|
||||||
|
slotRequest = new SlotRequest(uploadService.getAddress(), filename, fileSize, contentType);
|
||||||
|
break;
|
||||||
|
case v0_2:
|
||||||
|
slotRequest = new SlotRequest_V0_2(uploadService.getAddress(), filename, fileSize, contentType);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection.createStanzaCollectorAndSend(slotRequest).nextResultOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadFile(File file, URL putUrl, UploadProgressListener listener) throws IOException {
|
public void setTlsContext(SSLContext tlsContext) {
|
||||||
|
if (tlsContext == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.tlsSocketFactory = tlsContext.getSocketFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useTlsSettingsFrom(ConnectionConfiguration connectionConfiguration) {
|
||||||
|
SSLContext sslContext = connectionConfiguration.getCustomSSLContext();
|
||||||
|
setTlsContext(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uploadFile(final File file, final Slot slot, UploadProgressListener listener) throws IOException {
|
||||||
|
final long fileSize = file.length();
|
||||||
|
// TODO Remove once Smack's minimum Android API level is 19 or higher. See also comment below.
|
||||||
|
if (fileSize >= Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("File size " + fileSize + " must be less than " + Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
final int fileSizeInt = (int) fileSize;
|
||||||
|
|
||||||
|
// Construct the FileInputStream first to make sure we can actually read the file.
|
||||||
|
final FileInputStream fis = new FileInputStream(file);
|
||||||
|
|
||||||
|
final URL putUrl = slot.getPutUrl();
|
||||||
|
|
||||||
final HttpURLConnection urlConnection = (HttpURLConnection) putUrl.openConnection();
|
final HttpURLConnection urlConnection = (HttpURLConnection) putUrl.openConnection();
|
||||||
|
|
||||||
urlConnection.setRequestMethod("PUT");
|
urlConnection.setRequestMethod("PUT");
|
||||||
urlConnection.setUseCaches(false);
|
urlConnection.setUseCaches(false);
|
||||||
urlConnection.setDoOutput(true);
|
urlConnection.setDoOutput(true);
|
||||||
|
// TODO Change to using fileSize once Smack's minimum Android API level is 19 or higher.
|
||||||
|
urlConnection.setFixedLengthStreamingMode(fileSizeInt);
|
||||||
urlConnection.setRequestProperty("Content-Type", "application/octet-stream;");
|
urlConnection.setRequestProperty("Content-Type", "application/octet-stream;");
|
||||||
OutputStream outputStream = urlConnection.getOutputStream();
|
for (Entry<String, String> header : slot.getHeaders().entrySet()) {
|
||||||
|
urlConnection.setRequestProperty(header.getKey(), header.getValue());
|
||||||
long bytesSend = 0;
|
|
||||||
|
|
||||||
long fileSize = file.length();
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onUploadProgress(0, fileSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
final SSLSocketFactory tlsSocketFactory = this.tlsSocketFactory;
|
||||||
|
if (tlsSocketFactory != null && urlConnection instanceof HttpsURLConnection) {
|
||||||
|
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
|
||||||
|
httpsUrlConnection.setSSLSocketFactory(tlsSocketFactory);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] buffer = new byte[4096];
|
try {
|
||||||
int bytesRead;
|
OutputStream outputStream = urlConnection.getOutputStream();
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead);
|
long bytesSend = 0;
|
||||||
outputStream.flush();
|
|
||||||
bytesSend += bytesRead;
|
|
||||||
|
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onUploadProgress(bytesSend, fileSize);
|
listener.onUploadProgress(0, fileSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BufferedInputStream inputStream = new BufferedInputStream(fis);
|
||||||
|
|
||||||
|
// TODO Factor in extra static method (and re-use e.g. in bytestream code).
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
try {
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
bytesSend += bytesRead;
|
||||||
|
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onUploadProgress(bytesSend, fileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Exception while closing input stream", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Exception while closing output stream", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = urlConnection.getResponseCode();
|
||||||
|
switch (status) {
|
||||||
|
case HttpURLConnection.HTTP_OK:
|
||||||
|
case HttpURLConnection.HTTP_CREATED:
|
||||||
|
case HttpURLConnection.HTTP_NO_CONTENT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IOException("Error response " + status + " from server during file upload: "
|
||||||
|
+ urlConnection.getResponseMessage() + ", file size: " + fileSize + ", put URL: "
|
||||||
|
+ putUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
inputStream.close();
|
urlConnection.disconnect();
|
||||||
outputStream.close();
|
|
||||||
|
|
||||||
int status = urlConnection.getResponseCode();
|
|
||||||
if (status != HttpURLConnection.HTTP_CREATED) {
|
|
||||||
throw new IOException("Error response from server during file upload:"
|
|
||||||
+ " " + urlConnection.getResponseCode()
|
|
||||||
+ " " + urlConnection.getResponseMessage()
|
|
||||||
+ ", file size: " + fileSize
|
|
||||||
+ ", put URL: " + putUrl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean containsHttpFileUploadNamespace(DiscoverInfo discoverInfo) {
|
||||||
|
return discoverInfo.containsFeature(NAMESPACE) || discoverInfo.containsFeature(NAMESPACE_0_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright © 2017 Grigory Fedorov
|
||||||
|
*
|
||||||
|
* 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.httpfileupload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface to get upload progress.
|
||||||
|
*/
|
||||||
|
public interface UploadProgressListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for displaying upload progress.
|
||||||
|
*
|
||||||
|
* @param uploadedBytes the number of bytes uploaded at the moment
|
||||||
|
* @param totalBytes the total number of bytes to be uploaded
|
||||||
|
*/
|
||||||
|
void onUploadProgress(long uploadedBytes, long totalBytes);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright © 2017 Florian Schmaus
|
||||||
|
*
|
||||||
|
* 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.httpfileupload;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
|
public class UploadService {
|
||||||
|
|
||||||
|
enum Version {
|
||||||
|
v0_2,
|
||||||
|
v0_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
private final DomainBareJid address;
|
||||||
|
private final Version version;
|
||||||
|
private final Long maxFileSize;
|
||||||
|
|
||||||
|
UploadService(DomainBareJid address, Version version) {
|
||||||
|
this(address, version, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadService(DomainBareJid address, Version version, Long maxFileSize) {
|
||||||
|
this.address = Objects.requireNonNull(address);
|
||||||
|
this.version = version;
|
||||||
|
this.maxFileSize = maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DomainBareJid getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMaxFileSizeLimit() {
|
||||||
|
return maxFileSize != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getMaxFileSize() {
|
||||||
|
return maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean acceptsFileOfSize(long size) {
|
||||||
|
if (!hasMaxFileSizeLimit()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size <= maxFileSize;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,9 +32,15 @@ public class FileTooLargeError implements ExtensionElement {
|
||||||
public static final String NAMESPACE = SlotRequest.NAMESPACE;
|
public static final String NAMESPACE = SlotRequest.NAMESPACE;
|
||||||
|
|
||||||
private final long maxFileSize;
|
private final long maxFileSize;
|
||||||
|
private final String namespace;
|
||||||
|
|
||||||
public FileTooLargeError(long maxFileSize) {
|
public FileTooLargeError(long maxFileSize) {
|
||||||
|
this(maxFileSize, NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected FileTooLargeError(long maxFileSize, String namespace) {
|
||||||
this.maxFileSize = maxFileSize;
|
this.maxFileSize = maxFileSize;
|
||||||
|
this.namespace = namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMaxFileSize() {
|
public long getMaxFileSize() {
|
||||||
|
@ -48,15 +54,15 @@ public class FileTooLargeError implements ExtensionElement {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
return NAMESPACE;
|
return namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML() {
|
public XmlStringBuilder toXML() {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
xml.element("max-file-size", String.valueOf(maxFileSize));
|
xml.element("max-file-size", String.valueOf(maxFileSize));
|
||||||
xml.closeElement(ELEMENT);
|
xml.closeElement(this);
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Florian Schmaus
|
||||||
|
*
|
||||||
|
* 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.httpfileupload.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
|
||||||
|
|
||||||
|
public class FileTooLargeError_V0_2 extends FileTooLargeError {
|
||||||
|
|
||||||
|
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE_0_2;
|
||||||
|
|
||||||
|
public FileTooLargeError_V0_2(long maxFileSize) {
|
||||||
|
super(maxFileSize, NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ package org.jivesoftware.smackx.httpfileupload.element;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slot responded by upload service.
|
* Slot responded by upload service.
|
||||||
|
@ -33,12 +35,26 @@ public class Slot extends IQ {
|
||||||
|
|
||||||
private final URL putUrl;
|
private final URL putUrl;
|
||||||
private final URL getUrl;
|
private final URL getUrl;
|
||||||
|
private final Map<String, String> headers;
|
||||||
|
|
||||||
public Slot(URL putUrl, URL getUrl) {
|
public Slot(URL putUrl, URL getUrl) {
|
||||||
super(ELEMENT, NAMESPACE);
|
this(putUrl, getUrl, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slot(URL putUrl, URL getUrl, Map<String, String> headers) {
|
||||||
|
this(putUrl, getUrl, headers, NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Slot(URL putUrl, URL getUrl, Map<String, String> headers, String namespace) {
|
||||||
|
super(ELEMENT, namespace);
|
||||||
setType(Type.result);
|
setType(Type.result);
|
||||||
this.putUrl = putUrl;
|
this.putUrl = putUrl;
|
||||||
this.getUrl = getUrl;
|
this.getUrl = getUrl;
|
||||||
|
if (headers == null) {
|
||||||
|
this.headers = Collections.emptyMap();
|
||||||
|
} else {
|
||||||
|
this.headers = Collections.unmodifiableMap(headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public URL getPutUrl() {
|
public URL getPutUrl() {
|
||||||
|
@ -49,6 +65,9 @@ public class Slot extends IQ {
|
||||||
return getUrl;
|
return getUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getHeaders() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
||||||
|
@ -56,6 +75,9 @@ public class Slot extends IQ {
|
||||||
|
|
||||||
xml.element("put", putUrl.toString());
|
xml.element("put", putUrl.toString());
|
||||||
xml.element("get", getUrl.toString());
|
xml.element("get", getUrl.toString());
|
||||||
|
for (Map.Entry<String, String> entry : getHeaders().entrySet()) {
|
||||||
|
xml.openElement("header").attribute(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.jivesoftware.smackx.httpfileupload.element;
|
package org.jivesoftware.smackx.httpfileupload.element;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload slot request.
|
* Upload slot request.
|
||||||
|
@ -26,23 +28,31 @@ import org.jivesoftware.smack.packet.IQ;
|
||||||
*/
|
*/
|
||||||
public class SlotRequest extends IQ {
|
public class SlotRequest extends IQ {
|
||||||
public static final String ELEMENT = "request";
|
public static final String ELEMENT = "request";
|
||||||
public static final String NAMESPACE = "urn:xmpp:http:upload";
|
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE;
|
||||||
|
|
||||||
private final String filename;
|
private final String filename;
|
||||||
private final long size;
|
private final long size;
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
|
||||||
|
public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size) {
|
||||||
|
this(uploadServiceAddress, filename, size, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new slot request.
|
* Create new slot request.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if size is less than or equal to zero
|
* @param uploadServiceAddress the XMPP address of the service to request the slot from.
|
||||||
* @param filename name of file
|
* @param filename name of file
|
||||||
* @param size size of file in bytes
|
* @param size size of file in bytes
|
||||||
* @param contentType file content type or null
|
* @param contentType file content type or null
|
||||||
|
* @throws IllegalArgumentException if size is less than or equal to zero
|
||||||
*/
|
*/
|
||||||
public SlotRequest(String filename, long size, String contentType) {
|
public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) {
|
||||||
super(ELEMENT, NAMESPACE);
|
this(uploadServiceAddress, filename, size, contentType, NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size, String contentType, String namespace) {
|
||||||
|
super(ELEMENT, namespace);
|
||||||
|
|
||||||
if (size <= 0) {
|
if (size <= 0) {
|
||||||
throw new IllegalArgumentException("File fileSize must be greater than zero.");
|
throw new IllegalArgumentException("File fileSize must be greater than zero.");
|
||||||
|
@ -53,10 +63,7 @@ public class SlotRequest extends IQ {
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
|
|
||||||
setType(Type.get);
|
setType(Type.get);
|
||||||
}
|
setTo(uploadServiceAddress);
|
||||||
|
|
||||||
public SlotRequest(String filename, long size) {
|
|
||||||
this(filename, size, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
|
@ -76,9 +83,7 @@ public class SlotRequest extends IQ {
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
xml.element("filename", filename);
|
xml.element("filename", filename);
|
||||||
xml.element("size", String.valueOf(size));
|
xml.element("size", String.valueOf(size));
|
||||||
if (contentType != null) {
|
xml.optElement("content-type", contentType);
|
||||||
xml.element("content-type", contentType);
|
|
||||||
}
|
|
||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Florian Schmaus
|
||||||
|
*
|
||||||
|
* 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.httpfileupload.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
|
||||||
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
|
public class SlotRequest_V0_2 extends SlotRequest {
|
||||||
|
|
||||||
|
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE_0_2;
|
||||||
|
|
||||||
|
public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size) {
|
||||||
|
this(uploadServiceAddress, filename, size, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new slot request.
|
||||||
|
*
|
||||||
|
* @param uploadServiceAddress the XMPP address of the service to request the slot from.
|
||||||
|
* @param filename name of file
|
||||||
|
* @param size size of file in bytes
|
||||||
|
* @param contentType file content type or null
|
||||||
|
* @throws IllegalArgumentException if size is less than or equal to zero
|
||||||
|
*/
|
||||||
|
public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) {
|
||||||
|
super(uploadServiceAddress, filename, size, contentType, NAMESPACE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Florian Schmaus
|
||||||
|
*
|
||||||
|
* 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.httpfileupload.element;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
|
||||||
|
|
||||||
|
public class Slot_V0_2 extends Slot {
|
||||||
|
|
||||||
|
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE_0_2;
|
||||||
|
|
||||||
|
public Slot_V0_2(URL putUrl, URL getUrl) {
|
||||||
|
super(putUrl, getUrl, null, NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package org.jivesoftware.smackx.httpfileupload.provider;
|
||||||
|
|
||||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||||
import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
|
import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError_V0_2;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,6 +31,7 @@ public class FileTooLargeErrorProvider extends ExtensionElementProvider<FileTooL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileTooLargeError parse(XmlPullParser parser, int initialDepth) throws Exception {
|
public FileTooLargeError parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||||
|
final String namespace = parser.getNamespace();
|
||||||
Long maxFileSize = null;
|
Long maxFileSize = null;
|
||||||
|
|
||||||
outerloop: while(true) {
|
outerloop: while(true) {
|
||||||
|
@ -52,6 +54,13 @@ public class FileTooLargeErrorProvider extends ExtensionElementProvider<FileTooL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FileTooLargeError(maxFileSize);
|
switch (namespace) {
|
||||||
|
case FileTooLargeError.NAMESPACE:
|
||||||
|
return new FileTooLargeError(maxFileSize);
|
||||||
|
case FileTooLargeError_V0_2.NAMESPACE:
|
||||||
|
return new FileTooLargeError_V0_2(maxFileSize);
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,16 @@ package org.jivesoftware.smackx.httpfileupload.provider;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.provider.IQProvider;
|
import org.jivesoftware.smack.provider.IQProvider;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
import org.jivesoftware.smackx.httpfileupload.element.Slot;
|
import org.jivesoftware.smackx.httpfileupload.element.Slot;
|
||||||
|
import org.jivesoftware.smackx.httpfileupload.element.Slot_V0_2;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provider for Slot.
|
* Provider for Slot.
|
||||||
|
@ -35,8 +39,10 @@ public class SlotProvider extends IQProvider<Slot> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Slot parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException {
|
public Slot parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException {
|
||||||
|
final String namespace = parser.getNamespace();
|
||||||
URL putUrl = null;
|
URL putUrl = null;
|
||||||
URL getUrl = null;
|
URL getUrl = null;
|
||||||
|
Map<String, String> headers = null;
|
||||||
|
|
||||||
outerloop: while (true) {
|
outerloop: while (true) {
|
||||||
int event = parser.next();
|
int event = parser.next();
|
||||||
|
@ -51,6 +57,14 @@ public class SlotProvider extends IQProvider<Slot> {
|
||||||
case "get":
|
case "get":
|
||||||
getUrl = new URL(parser.nextText());
|
getUrl = new URL(parser.nextText());
|
||||||
break;
|
break;
|
||||||
|
case "header":
|
||||||
|
String headerName = ParserUtils.getRequiredAttribute(parser, "name");
|
||||||
|
String headerValue = ParserUtils.getRequiredNextText(parser);
|
||||||
|
if (headers == null) {
|
||||||
|
headers = new HashMap<>();
|
||||||
|
}
|
||||||
|
headers.put(headerName, headerValue);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case XmlPullParser.END_TAG:
|
case XmlPullParser.END_TAG:
|
||||||
|
@ -61,6 +75,13 @@ public class SlotProvider extends IQProvider<Slot> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Slot(putUrl, getUrl);
|
switch (namespace) {
|
||||||
|
case Slot.NAMESPACE:
|
||||||
|
return new Slot(putUrl, getUrl, headers);
|
||||||
|
case Slot_V0_2.NAMESPACE:
|
||||||
|
return new Slot_V0_2(putUrl, getUrl);
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,6 +234,16 @@
|
||||||
</extensionProvider>
|
</extensionProvider>
|
||||||
|
|
||||||
<!-- XEP-0363: HTTP File Upload -->
|
<!-- XEP-0363: HTTP File Upload -->
|
||||||
|
<iqProvider>
|
||||||
|
<elementName>slot</elementName>
|
||||||
|
<namespace>urn:xmpp:http:upload:0</namespace>
|
||||||
|
<className>org.jivesoftware.smackx.httpfileupload.provider.SlotProvider</className>
|
||||||
|
</iqProvider>
|
||||||
|
<extensionProvider>
|
||||||
|
<elementName>file-too-large</elementName>
|
||||||
|
<namespace>urn:xmpp:http:upload:0</namespace>
|
||||||
|
<className>org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider</className>
|
||||||
|
</extensionProvider>
|
||||||
<iqProvider>
|
<iqProvider>
|
||||||
<elementName>slot</elementName>
|
<elementName>slot</elementName>
|
||||||
<namespace>urn:xmpp:http:upload</namespace>
|
<namespace>urn:xmpp:http:upload</namespace>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class FileTooLargeErrorCreateTest {
|
public class FileTooLargeErrorCreateTest {
|
||||||
String fileTooLargeErrorExtensionExample
|
String fileTooLargeErrorExtensionExample
|
||||||
= "<file-too-large xmlns='urn:xmpp:http:upload'>"
|
= "<file-too-large xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<max-file-size>20000</max-file-size>"
|
+ "<max-file-size>20000</max-file-size>"
|
||||||
+ "</file-too-large>";
|
+ "</file-too-large>";
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import java.net.URL;
|
||||||
|
|
||||||
public class SlotCreateTest {
|
public class SlotCreateTest {
|
||||||
String testSlot
|
String testSlot
|
||||||
= "<slot xmlns='urn:xmpp:http:upload'>"
|
= "<slot xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<put>https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</put>"
|
+ "<put>https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</put>"
|
||||||
+ "<get>https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</get>"
|
+ "<get>https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</get>"
|
||||||
+ "</slot>";
|
+ "</slot>";
|
||||||
|
|
|
@ -19,27 +19,28 @@ package org.jivesoftware.smackx.httpfileupload;
|
||||||
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
|
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.jxmpp.jid.JidTestUtil;
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
|
||||||
public class SlotRequestCreateTest {
|
public class SlotRequestCreateTest {
|
||||||
|
|
||||||
String testRequest
|
String testRequest
|
||||||
= "<request xmlns='urn:xmpp:http:upload'>"
|
= "<request xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<filename>my_juliet.png</filename>"
|
+ "<filename>my_juliet.png</filename>"
|
||||||
+ "<size>23456</size>"
|
+ "<size>23456</size>"
|
||||||
+ "<content-type>image/jpeg</content-type>"
|
+ "<content-type>image/jpeg</content-type>"
|
||||||
+ "</request>";
|
+ "</request>";
|
||||||
|
|
||||||
String testRequestWithoutContentType
|
String testRequestWithoutContentType
|
||||||
= "<request xmlns='urn:xmpp:http:upload'>"
|
= "<request xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<filename>my_romeo.png</filename>"
|
+ "<filename>my_romeo.png</filename>"
|
||||||
+ "<size>52523</size>"
|
+ "<size>52523</size>"
|
||||||
+ "</request>";
|
+ "</request>";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkSlotRequestCreation() throws XmppStringprepException {
|
public void checkSlotRequestCreation() throws XmppStringprepException {
|
||||||
SlotRequest slotRequest = new SlotRequest("my_juliet.png", 23456, "image/jpeg");
|
SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 23456, "image/jpeg");
|
||||||
|
|
||||||
Assert.assertEquals("my_juliet.png", slotRequest.getFilename());
|
Assert.assertEquals("my_juliet.png", slotRequest.getFilename());
|
||||||
Assert.assertEquals(23456, slotRequest.getSize());
|
Assert.assertEquals(23456, slotRequest.getSize());
|
||||||
|
@ -50,7 +51,7 @@ public class SlotRequestCreateTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkSlotRequestCreationWithoutContentType() throws XmppStringprepException {
|
public void checkSlotRequestCreationWithoutContentType() throws XmppStringprepException {
|
||||||
SlotRequest slotRequest = new SlotRequest("my_romeo.png", 52523);
|
SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_romeo.png", 52523);
|
||||||
|
|
||||||
Assert.assertEquals("my_romeo.png", slotRequest.getFilename());
|
Assert.assertEquals("my_romeo.png", slotRequest.getFilename());
|
||||||
Assert.assertEquals(52523, slotRequest.getSize());
|
Assert.assertEquals(52523, slotRequest.getSize());
|
||||||
|
@ -61,11 +62,11 @@ public class SlotRequestCreateTest {
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void checkSlotRequestCreationNegativeSize() {
|
public void checkSlotRequestCreationNegativeSize() {
|
||||||
new SlotRequest("my_juliet.png", -23456, "image/jpeg");
|
new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", -23456, "image/jpeg");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void checkSlotRequestCreationZeroSize() {
|
public void checkSlotRequestCreationZeroSize() {
|
||||||
new SlotRequest("my_juliet.png", 0, "image/jpeg");
|
new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 0, "image/jpeg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.httpfileupload;
|
package org.jivesoftware.smackx.httpfileupload.provider;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
@ -33,14 +33,14 @@ public class FileTooLargeErrorProviderTest {
|
||||||
+ "id='step_03' "
|
+ "id='step_03' "
|
||||||
+ "to='romeo@montague.tld/garden' "
|
+ "to='romeo@montague.tld/garden' "
|
||||||
+ "type='error'>"
|
+ "type='error'>"
|
||||||
+ "<request xmlns='urn:xmpp:http:upload'>"
|
+ "<request xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<filename>my_juliet.png</filename>"
|
+ "<filename>my_juliet.png</filename>"
|
||||||
+ "<size>23456</size>"
|
+ "<size>23456</size>"
|
||||||
+ "</request>"
|
+ "</request>"
|
||||||
+ "<error type='modify'>"
|
+ "<error type='modify'>"
|
||||||
+ "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' />"
|
+ "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' />"
|
||||||
+ "<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File too large. The maximum file size is 20000 bytes</text>"
|
+ "<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File too large. The maximum file size is 20000 bytes</text>"
|
||||||
+ "<file-too-large xmlns='urn:xmpp:http:upload'>"
|
+ "<file-too-large xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<max-file-size>20000</max-file-size>"
|
+ "<max-file-size>20000</max-file-size>"
|
||||||
+ "</file-too-large>"
|
+ "</file-too-large>"
|
||||||
+ "</error>"
|
+ "</error>"
|
|
@ -14,15 +14,13 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.httpfileupload;
|
package org.jivesoftware.smackx.httpfileupload.provider;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smackx.httpfileupload.element.Slot;
|
import org.jivesoftware.smackx.httpfileupload.element.Slot;
|
||||||
import org.jivesoftware.smackx.httpfileupload.provider.SlotProvider;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
@ -38,7 +36,7 @@ public class SlotProviderTest {
|
||||||
+ "id='step_03' "
|
+ "id='step_03' "
|
||||||
+ "to='romeo@montague.tld/garden' "
|
+ "to='romeo@montague.tld/garden' "
|
||||||
+ "type='result'>"
|
+ "type='result'>"
|
||||||
+ "<slot xmlns='urn:xmpp:http:upload'>"
|
+ "<slot xmlns='urn:xmpp:http:upload:0'>"
|
||||||
+ "<put>https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</put>"
|
+ "<put>https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</put>"
|
||||||
+ "<get>https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</get>"
|
+ "<get>https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</get>"
|
||||||
+ "</slot>"
|
+ "</slot>"
|
||||||
|
@ -46,8 +44,7 @@ public class SlotProviderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkSlotProvider() throws Exception {
|
public void checkSlotProvider() throws Exception {
|
||||||
XmlPullParser parser = PacketParserUtils.getParserFor(slotExample);
|
Slot slot = PacketParserUtils.parseStanza(slotExample);
|
||||||
Slot slot = new SlotProvider().parse(parser);
|
|
||||||
|
|
||||||
Assert.assertEquals(IQ.Type.result, slot.getType());
|
Assert.assertEquals(IQ.Type.result, slot.getType());
|
||||||
Assert.assertEquals(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
|
Assert.assertEquals(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
|
|
@ -16,10 +16,16 @@
|
||||||
*/
|
*/
|
||||||
package org.igniterealtime.smack.inttest;
|
package org.igniterealtime.smack.inttest;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
import org.jivesoftware.smack.StanzaCollector;
|
import org.jivesoftware.smack.StanzaCollector;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
@ -37,9 +43,12 @@ public abstract class AbstractSmackIntTest {
|
||||||
|
|
||||||
protected final long timeout;
|
protected final long timeout;
|
||||||
|
|
||||||
protected AbstractSmackIntTest(String testRunId, long timeout) {
|
protected final Configuration sinttestConfiguration;
|
||||||
|
|
||||||
|
protected AbstractSmackIntTest(String testRunId, Configuration configuration) {
|
||||||
this.testRunId = testRunId;
|
this.testRunId = testRunId;
|
||||||
this.timeout = timeout;
|
this.sinttestConfiguration = configuration;
|
||||||
|
this.timeout = configuration.replyTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void performActionAndWaitUntilStanzaReceived(Runnable action, XMPPConnection connection, StanzaFilter filter)
|
protected void performActionAndWaitUntilStanzaReceived(Runnable action, XMPPConnection connection, StanzaFilter filter)
|
||||||
|
@ -71,4 +80,19 @@ public abstract class AbstractSmackIntTest {
|
||||||
protected interface Condition {
|
protected interface Condition {
|
||||||
boolean evaluate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
|
boolean evaluate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected File createNewTempFile() throws IOException {
|
||||||
|
File file = File.createTempFile("smack-integration-test-" + testRunId + "-temp-file", null);
|
||||||
|
file.deleteOnExit();
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpURLConnection getHttpUrlConnectionFor(URL url) throws IOException {
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||||
|
if (sinttestConfiguration.tlsContext != null && urlConnection instanceof HttpsURLConnection) {
|
||||||
|
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
|
||||||
|
httpsUrlConnection.setSSLSocketFactory(sinttestConfiguration.tlsContext.getSocketFactory());
|
||||||
|
}
|
||||||
|
return urlConnection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
|
||||||
protected final XMPPConnection connection;
|
protected final XMPPConnection connection;
|
||||||
|
|
||||||
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
|
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
|
||||||
super(environment.testRunId, environment.configuration.replyTimeout);
|
super(environment.testRunId, environment.configuration);
|
||||||
this.connection = this.conOne = environment.conOne;
|
this.connection = this.conOne = environment.conOne;
|
||||||
this.conTwo = environment.conTwo;
|
this.conTwo = environment.conTwo;
|
||||||
this.conThree = environment.conThree;
|
this.conThree = environment.conThree;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015-2016 Florian Schmaus
|
* Copyright 2015-2017 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,14 +19,10 @@ package org.igniterealtime.smack.inttest;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
|
||||||
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
|
|
||||||
import eu.geekplace.javapinning.java7.Java7Pinning;
|
|
||||||
|
|
||||||
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
|
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
|
||||||
|
|
||||||
private final SmackIntegrationTestEnvironment environment;
|
private final SmackIntegrationTestEnvironment environment;
|
||||||
|
@ -39,7 +35,7 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
|
||||||
protected final DomainBareJid service;
|
protected final DomainBareJid service;
|
||||||
|
|
||||||
public AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
|
public AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
|
||||||
super(environment.testRunId, environment.configuration.replyTimeout);
|
super(environment.testRunId, environment.configuration);
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.configuration = environment.configuration;
|
this.configuration = environment.configuration;
|
||||||
this.service = configuration.service;
|
this.service = configuration.service;
|
||||||
|
@ -47,9 +43,8 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
|
||||||
|
|
||||||
public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException {
|
public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
|
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
|
||||||
if (configuration.serviceTlsPin != null) {
|
if (configuration.tlsContext != null) {
|
||||||
SSLContext sc = Java7Pinning.forPin(configuration.serviceTlsPin);
|
builder.setCustomSSLContext(configuration.tlsContext);
|
||||||
builder.setCustomSSLContext(sc);
|
|
||||||
}
|
}
|
||||||
builder.setSecurityMode(configuration.securityMode);
|
builder.setSecurityMode(configuration.securityMode);
|
||||||
builder.setXmppDomain(service);
|
builder.setXmppDomain(service);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015-2016 Florian Schmaus
|
* Copyright 2015-2017 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,6 +19,8 @@ package org.igniterealtime.smack.inttest;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,6 +28,8 @@ import java.util.Map.Entry;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
@ -33,6 +37,8 @@ import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
|
import eu.geekplace.javapinning.java7.Java7Pinning;
|
||||||
|
|
||||||
public final class Configuration {
|
public final class Configuration {
|
||||||
|
|
||||||
public enum AccountRegistration {
|
public enum AccountRegistration {
|
||||||
|
@ -45,6 +51,8 @@ public final class Configuration {
|
||||||
|
|
||||||
public final String serviceTlsPin;
|
public final String serviceTlsPin;
|
||||||
|
|
||||||
|
public final SSLContext tlsContext;
|
||||||
|
|
||||||
public final SecurityMode securityMode;
|
public final SecurityMode securityMode;
|
||||||
|
|
||||||
public final int replyTimeout;
|
public final int replyTimeout;
|
||||||
|
@ -78,10 +86,16 @@ public final class Configuration {
|
||||||
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
|
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
|
||||||
boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
|
boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
|
||||||
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
|
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
|
||||||
Set<String> testPackages, String adminAccountUsername, String adminAccountPassword) {
|
Set<String> testPackages, String adminAccountUsername, String adminAccountPassword)
|
||||||
|
throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
this.service = Objects.requireNonNull(service,
|
this.service = Objects.requireNonNull(service,
|
||||||
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
|
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
|
||||||
this.serviceTlsPin = serviceTlsPin;
|
this.serviceTlsPin = serviceTlsPin;
|
||||||
|
if (serviceTlsPin != null) {
|
||||||
|
tlsContext = Java7Pinning.forPin(serviceTlsPin);
|
||||||
|
} else {
|
||||||
|
tlsContext = null;
|
||||||
|
}
|
||||||
this.securityMode = securityMode;
|
this.securityMode = securityMode;
|
||||||
if (replyTimeout > 0) {
|
if (replyTimeout > 0) {
|
||||||
this.replyTimeout = replyTimeout;
|
this.replyTimeout = replyTimeout;
|
||||||
|
@ -257,7 +271,7 @@ public final class Configuration {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Configuration build() {
|
public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername,
|
return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername,
|
||||||
accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests,
|
accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests,
|
||||||
testPackages, adminAccountUsername, adminAccountPassword);
|
testPackages, adminAccountUsername, adminAccountPassword);
|
||||||
|
@ -266,7 +280,8 @@ public final class Configuration {
|
||||||
|
|
||||||
private static final String SINTTEST = "sinttest.";
|
private static final String SINTTEST = "sinttest.";
|
||||||
|
|
||||||
public static Configuration newConfiguration() throws IOException {
|
public static Configuration newConfiguration()
|
||||||
|
throws IOException, KeyManagementException, NoSuchAlgorithmException {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
|
||||||
File propertiesFile = findPropertiesFile();
|
File propertiesFile = findPropertiesFile();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015-2016 Florian Schmaus
|
* Copyright 2015-2017 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -43,8 +43,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
|
|
||||||
import org.igniterealtime.smack.inttest.IntTestUtil.UsernameAndPassword;
|
import org.igniterealtime.smack.inttest.IntTestUtil.UsernameAndPassword;
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
import org.jivesoftware.smack.SmackConfiguration;
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
|
@ -64,8 +62,6 @@ import org.reflections.scanners.MethodParameterScanner;
|
||||||
import org.reflections.scanners.SubTypesScanner;
|
import org.reflections.scanners.SubTypesScanner;
|
||||||
import org.reflections.scanners.TypeAnnotationsScanner;
|
import org.reflections.scanners.TypeAnnotationsScanner;
|
||||||
|
|
||||||
import eu.geekplace.javapinning.java7.Java7Pinning;
|
|
||||||
|
|
||||||
public class SmackIntegrationTestFramework {
|
public class SmackIntegrationTestFramework {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(SmackIntegrationTestFramework.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(SmackIntegrationTestFramework.class.getName());
|
||||||
|
@ -553,9 +549,8 @@ public class SmackIntegrationTestFramework {
|
||||||
.setResource(middlefix + '-' + testRunResult.testRunId)
|
.setResource(middlefix + '-' + testRunResult.testRunId)
|
||||||
.setSecurityMode(config.securityMode);
|
.setSecurityMode(config.securityMode);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
if (StringUtils.isNotEmpty(config.serviceTlsPin)) {
|
if (config.tlsContext != null) {
|
||||||
SSLContext sc = Java7Pinning.forPin(config.serviceTlsPin);
|
builder.setCustomSSLContext(config.tlsContext);
|
||||||
builder.setCustomSSLContext(sc);
|
|
||||||
}
|
}
|
||||||
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
|
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
|
||||||
connection.connect();
|
connection.connect();
|
||||||
|
@ -581,9 +576,8 @@ public class SmackIntegrationTestFramework {
|
||||||
SmackException, IOException, XMPPException {
|
SmackException, IOException, XMPPException {
|
||||||
Configuration config = environment.configuration;
|
Configuration config = environment.configuration;
|
||||||
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
|
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
|
||||||
if (config.serviceTlsPin != null) {
|
if (config.tlsContext != null) {
|
||||||
SSLContext sc = Java7Pinning.forPin(config.serviceTlsPin);
|
builder.setCustomSSLContext(config.tlsContext);
|
||||||
builder.setCustomSSLContext(sc);
|
|
||||||
}
|
}
|
||||||
builder.setSecurityMode(config.securityMode);
|
builder.setSecurityMode(config.securityMode);
|
||||||
builder.setXmppDomain(config.service);
|
builder.setXmppDomain(config.service);
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Florian Schmaus
|
||||||
|
*
|
||||||
|
* 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.httpfileupload;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
||||||
|
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||||
|
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||||
|
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
|
|
||||||
|
public class HttpFileUploadIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
|
|
||||||
|
private static final int FILE_SIZE = 1024*128;
|
||||||
|
|
||||||
|
private final HttpFileUploadManager hfumOne;
|
||||||
|
|
||||||
|
public HttpFileUploadIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPErrorException,
|
||||||
|
NotConnectedException, NoResponseException, InterruptedException, TestNotPossibleException {
|
||||||
|
super(environment);
|
||||||
|
hfumOne = HttpFileUploadManager.getInstanceFor(conOne);
|
||||||
|
if (!hfumOne.discoverUploadService()) {
|
||||||
|
throw new TestNotPossibleException(
|
||||||
|
"HttpFileUploadManager was unable to discover a HTTP File Upload service");
|
||||||
|
}
|
||||||
|
UploadService uploadService = hfumOne.getDefaultUploadService();
|
||||||
|
if (!uploadService.acceptsFileOfSize(FILE_SIZE)) {
|
||||||
|
throw new TestNotPossibleException("The upload service at " + uploadService.getAddress()
|
||||||
|
+ " does not accept files of size " + FILE_SIZE
|
||||||
|
+ ". It only accepts files with a maximum size of " + uploadService.getMaxFileSize());
|
||||||
|
}
|
||||||
|
hfumOne.setTlsContext(environment.configuration.tlsContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void httpFileUploadTest() throws FileNotFoundException, IOException, XMPPErrorException, InterruptedException, SmackException {
|
||||||
|
final int fileSize = FILE_SIZE;
|
||||||
|
File file = createNewTempFile();
|
||||||
|
FileOutputStream fos = new FileOutputStream(file.getCanonicalPath());
|
||||||
|
byte[] upBytes;
|
||||||
|
try {
|
||||||
|
upBytes = new byte[fileSize];
|
||||||
|
INSECURE_RANDOM.nextBytes(upBytes);
|
||||||
|
fos.write(upBytes);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
URL getUrl = hfumOne.uploadFile(file, new UploadProgressListener() {
|
||||||
|
@Override
|
||||||
|
public void onUploadProgress(long uploadedBytes, long totalBytes) {
|
||||||
|
double progress = uploadedBytes / totalBytes;
|
||||||
|
LOGGER.fine("HTTP File Upload progress " + progress + "% (" + uploadedBytes + '/' + totalBytes + ')');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpURLConnection urlConnection = getHttpUrlConnectionFor(getUrl);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize);
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int n;
|
||||||
|
try {
|
||||||
|
InputStream is = new BufferedInputStream(urlConnection.getInputStream());
|
||||||
|
while ((n = is.read(buffer)) != -1) {
|
||||||
|
baos.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
urlConnection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] downBytes = baos.toByteArray();
|
||||||
|
|
||||||
|
assertArrayEquals(upBytes, downBytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../../../smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/package-info.java
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2015-2017 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,11 +16,16 @@
|
||||||
*/
|
*/
|
||||||
package org.igniterealtime.smack.inttest;
|
package org.igniterealtime.smack.inttest;
|
||||||
|
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.jxmpp.jid.JidTestUtil;
|
import org.jxmpp.jid.JidTestUtil;
|
||||||
|
|
||||||
public class SmackIntegrationTestUnitTestUtil {
|
public class SmackIntegrationTestUnitTestUtil {
|
||||||
|
|
||||||
public static DummySmackIntegrationTestFramework getFrameworkForUnitTest(Class<? extends AbstractSmackIntTest> unitTest) {
|
public static DummySmackIntegrationTestFramework getFrameworkForUnitTest(
|
||||||
|
Class<? extends AbstractSmackIntTest> unitTest)
|
||||||
|
throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
Configuration configuration = Configuration.builder()
|
Configuration configuration = Configuration.builder()
|
||||||
.setService(JidTestUtil.DOMAIN_BARE_JID_1)
|
.setService(JidTestUtil.DOMAIN_BARE_JID_1)
|
||||||
|
|
Loading…
Reference in a new issue