-----BEGIN PGP SIGNATURE----- iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAljDKBtfFIAAAAAALgAo aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF IFJB7Qf6AlkwpzMqq1g18jzEBFVX/3Sk2QWivEY7t3EhGuSguan2VIfd1fL0P85Q vLBm6Pw93haIxHXKRUKc8DINwP9yuRMvUotCN2hYVgfqfByHGhDCJLTNZ9atncL5 JToptfhdRy6kgljVZPtpXOMXKBvaO3QOuTuC5cmz8PlidsYw0yUnliPLQ36uPRWX eaEXXbgmkjJh35WjsaafD/uM86OCqZahfvEf3e8bkPzdAayd0OKU67+v0ArA9P2E CiRU5vfco/vt2Qo41aLLIEOjSFfVX6Xh/pXxfQvInMAxies0KRLi5vonOmfrWRmi uIblzcYRXCSaZSgVN2yF8KzmF4pzcw== =qETn -----END PGP SIGNATURE----- Merge tag '4.2.0' Smack 4.2.0filetransferTypos
commit
1a93b448db
@ -0,0 +1,463 @@
|
||||
/**
|
||||
*
|
||||
* 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;
|
||||
|
||||
import org.jivesoftware.smack.AbstractConnectionListener;
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
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.SlotRequest;
|
||||
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest_V0_2;
|
||||
import org.jivesoftware.smackx.xdata.FormField;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
import org.jxmpp.jid.DomainBareJid;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* A manager for XEP-0363: HTTP File Upload.
|
||||
*
|
||||
* @author Grigory Fedorov
|
||||
* @author Florian Schmaus
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||
*/
|
||||
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());
|
||||
|
||||
static {
|
||||
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
||||
@Override
|
||||
public void connectionCreated(XMPPConnection connection) {
|
||||
getInstanceFor(connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final Map<XMPPConnection, HttpFileUploadManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
private UploadService defaultUploadService;
|
||||
|
||||
private SSLSocketFactory tlsSocketFactory;
|
||||
|
||||
/**
|
||||
* Obtain the HttpFileUploadManager responsible for a connection.
|
||||
*
|
||||
* @param connection the connection object.
|
||||
* @return a HttpFileUploadManager instance
|
||||
*/
|
||||
public static synchronized HttpFileUploadManager getInstanceFor(XMPPConnection connection) {
|
||||
HttpFileUploadManager httpFileUploadManager = INSTANCES.get(connection);
|
||||
|
||||
if (httpFileUploadManager == null) {
|
||||
httpFileUploadManager = new HttpFileUploadManager(connection);
|
||||
INSTANCES.put(connection, httpFileUploadManager);
|
||||
}
|
||||
|
||||
return httpFileUploadManager;
|
||||
}
|
||||
|
||||
private HttpFileUploadManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
|
||||
connection.addConnectionListener(new AbstractConnectionListener() {
|
||||
@Override
|
||||
public void authenticated(XMPPConnection connection, boolean resumed) {
|
||||
// No need to reset the cache if the connection got resumed.
|
||||
if (resumed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
discoverUploadService();
|
||||
} catch (XMPPException.XMPPErrorException | SmackException.NotConnectedException
|
||||
| SmackException.NoResponseException | InterruptedException e) {
|
||||
LOGGER.log(Level.WARNING, "Error during discovering HTTP File Upload service", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* Called automatically when connection is authenticated.
|
||||
*
|
||||
* Note that this is a synchronous call -- Smack must wait for the server response.
|
||||
*
|
||||
* @return true if upload service was discovered
|
||||
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws InterruptedException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public boolean discoverUploadService() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||
InterruptedException, SmackException.NoResponseException {
|
||||
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||
List<DiscoverInfo> servicesDiscoverInfo = sdm
|
||||
.findServicesDiscoverInfo(NAMESPACE, true, true);
|
||||
|
||||
if (servicesDiscoverInfo.isEmpty()) {
|
||||
servicesDiscoverInfo = sdm.findServicesDiscoverInfo(NAMESPACE_0_2, true, true);
|
||||
if (servicesDiscoverInfo.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverInfo discoverInfo = servicesDiscoverInfo.get(0);
|
||||
|
||||
defaultUploadService = uploadServiceFrom(discoverInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if upload service was discovered.
|
||||
*
|
||||
* @return true if upload service was discovered
|
||||
*/
|
||||
public boolean isUploadServiceDiscovered() {
|
||||
return defaultUploadService != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default upload service if it was discovered.
|
||||
*
|
||||
* @return upload service JID or null if not available
|
||||
*/
|
||||
public UploadService getDefaultUploadService() {
|
||||
return defaultUploadService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request slot and uploaded file to HTTP file upload service.
|
||||
*
|
||||
* You don't need to request slot and upload file separately, this method will do both.
|
||||
* Note that this is a synchronous call -- Smack must wait for the server response.
|
||||
*
|
||||
* @param file file to be uploaded
|
||||
* @return public URL for sharing uploaded file
|
||||
* @throws InterruptedException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException
|
||||
* @throws IOException in case of HTTP upload errors
|
||||
*/
|
||||
public URL uploadFile(File file) throws InterruptedException, XMPPException.XMPPErrorException,
|
||||
SmackException, IOException {
|
||||
return uploadFile(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request slot and uploaded file to HTTP file upload service with progress callback.
|
||||
*
|
||||
* You don't need to request slot and upload file separately, this method will do both.
|
||||
* Note that this is a synchronous call -- Smack must wait for the server response.
|
||||
*
|
||||
* @param file file to be uploaded
|
||||
* @param listener upload progress listener of null
|
||||
* @return public URL for sharing uploaded file
|
||||
*
|
||||
* @throws InterruptedException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException
|
||||
* @throws IOException
|
||||
*/
|
||||
public URL uploadFile(File file, UploadProgressListener listener) throws InterruptedException,
|
||||
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");
|
||||
|
||||
uploadFile(file, slot, listener);
|
||||
|
||||
return slot.getGetUrl();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param filename name of file to be uploaded
|
||||
* @param fileSize file size in bytes.
|
||||
* @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
|
||||
* supported by the service.
|
||||
* @throws InterruptedException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public Slot requestSlot(String filename, long fileSize) throws InterruptedException,
|
||||
XMPPException.XMPPErrorException, SmackException {
|
||||
return requestSlot(filename, fileSize, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a new upload slot with optional content type 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.
|
||||
*
|
||||
* @param filename name of file to be uploaded
|
||||
* @param fileSize file size in bytes.
|
||||
* @param contentType file content-type or null
|
||||
* @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
|
||||
* supported by the service.
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws InterruptedException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public Slot requestSlot(String filename, long fileSize, String contentType) throws SmackException,
|
||||
InterruptedException, XMPPException.XMPPErrorException {
|
||||
return requestSlot(filename, fileSize, contentType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a new upload slot with optional content type from custom upload service.
|
||||
*
|
||||
* 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 fileSize file size in bytes.
|
||||
* @param contentType file content-type or null
|
||||
* @param uploadServiceAddress the address of the upload service to use or null for default one
|
||||
* @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
|
||||
* supported by the service.
|
||||
* @throws SmackException
|
||||
* @throws InterruptedException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
*/
|
||||
public Slot requestSlot(String filename, long fileSize, String contentType, DomainBareJid uploadServiceAddress)
|
||||
throws SmackException, InterruptedException, XMPPException.XMPPErrorException {
|
||||
final XMPPConnection connection = connection();
|
||||
final UploadService defaultUploadService = this.defaultUploadService;
|
||||
|
||||
// The upload service we are going to use.
|
||||
UploadService uploadService;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadService == null) {
|
||||
throw new SmackException("No upload service specified and also none discovered.");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
urlConnection.setRequestMethod("PUT");
|
||||
urlConnection.setUseCaches(false);
|
||||
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;");
|
||||
for (Entry<String, String> header : slot.getHeaders().entrySet()) {
|
||||
urlConnection.setRequestProperty(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
final SSLSocketFactory tlsSocketFactory = this.tlsSocketFactory;
|
||||
if (tlsSocketFactory != null && urlConnection instanceof HttpsURLConnection) {
|
||||
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
|
||||
httpsUrlConnection.setSSLSocketFactory(tlsSocketFactory);
|
||||
}
|
||||
|
||||
try {
|
||||
OutputStream outputStream = urlConnection.getOutputStream();
|
||||
|
||||
long bytesSend = 0;
|
||||
|
||||
if (listener != null) {
|
||||
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 {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
*
|
||||
* 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.element;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
* File Too Large error extension.
|
||||
*
|
||||
* @author Grigory Fedorov
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||
*/
|
||||
public class FileTooLargeError implements ExtensionElement {
|
||||
public static final String ELEMENT = "file-too-large";
|
||||
public static final String NAMESPACE = SlotRequest.NAMESPACE;
|
||||
|
||||
private final long maxFileSize;
|
||||
private final String namespace;
|
||||
|
||||
public FileTooLargeError(long maxFileSize) {
|
||||
this(maxFileSize, NAMESPACE);
|
||||
}
|
||||
|
||||
protected FileTooLargeError(long maxFileSize, String namespace) {
|
||||
this.maxFileSize = maxFileSize;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public long getMaxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngleBracket();
|
||||
xml.element("max-file-size", String.valueOf(maxFileSize));
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static FileTooLargeError from(IQ iq) {
|
||||
XMPPError error = iq.getError();
|
||||
if (error == null) {
|
||||
return null;
|
||||
}
|
||||
return error.getExtension(ELEMENT, NAMESPACE);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/**
|
||||
*
|
||||
* 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.element;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Slot responded by upload service.
|
||||
*
|
||||
* @author Grigory Fedorov
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||
*/
|
||||
public class Slot extends IQ {
|
||||
|
||||
public static final String ELEMENT = "slot";
|
||||
public static final String NAMESPACE = SlotRequest.NAMESPACE;
|
||||
|
||||
private final URL putUrl;
|
||||
private final URL getUrl;
|
||||
private final Map<String, String> headers;
|
||||
|
||||
public Slot(URL putUrl, URL getUrl) {
|
||||
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);
|
||||
this.putUrl = putUrl;
|
||||
this.getUrl = getUrl;
|
||||
if (headers == null) {
|
||||
this.headers = Collections.emptyMap();
|
||||
} else {
|
||||
this.headers = Collections.unmodifiableMap(headers);
|
||||
}
|
||||
}
|
||||
|
||||
public URL getPutUrl() {
|
||||
return putUrl;
|
||||
}
|
||||
|
||||
public URL getGetUrl() {
|
||||
return getUrl;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
||||
xml.rightAngleBracket();
|
||||
|
||||
xml.element("put", putUrl.toString());
|
||||
xml.element("get", getUrl.toString());
|
||||
for (Map.Entry<String, String> entry : getHeaders().entrySet()) {
|
||||
xml.openElement("header").attribute(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/**
|
||||
*
|
||||
* 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.element;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
|
||||
import org.jxmpp.jid.DomainBareJid;
|
||||
|
||||
/**
|
||||
* Upload slot request.
|
||||
|
||||
* @author Grigory Fedorov
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||
*/
|
||||
public class SlotRequest extends IQ {
|
||||
public static final String ELEMENT = "request";
|
||||
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE;
|
||||
|
||||
private final String filename;
|
||||
private final long size;
|
||||
private final String contentType;
|
||||
|
||||
public SlotRequest(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(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) {
|
||||
this(uploadServiceAddress, filename, size, contentType, NAMESPACE);
|
||||
}
|
||||
|
||||
protected SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size, String contentType, String namespace) {
|
||||
super(ELEMENT, namespace);
|
||||
|
||||
if (size <= 0) {
|
||||
throw new IllegalArgumentException("File fileSize must be greater than zero.");
|
||||
}
|
||||
|
||||
this.filename = filename;
|
||||
this.size = size;
|
||||
this.contentType = contentType;
|
||||
|
||||
setType(Type.get);
|
||||
setTo(uploadServiceAddress);
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
|
||||
xml.rightAngleBracket();
|
||||
xml.element("filename", filename);
|
||||
xml.element("size", String.valueOf(size));
|
||||
xml.optElement("content-type", contentType);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* IQ stanzas and extensions for XEP-0363: HTTP File Upload.
|
||||
*
|
||||
* @author Grigory Fedorov
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||
*/
|
||||
package org.jivesoftware.smackx.httpfileupload.element;
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smack's API for XEP-0363: HTTP File Upload.
|
||||
*
|
||||
* @author Grigory Fedorov
|
||||
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
|
||||