001package com.github.theholywaffle.teamspeak3; 002 003/* 004 * #%L 005 * TeamSpeak 3 Java API 006 * %% 007 * Copyright (C) 2016 Bert De Geyter, Roger Baumgartner 008 * %% 009 * Permission is hereby granted, free of charge, to any person obtaining a copy 010 * of this software and associated documentation files (the "Software"), to deal 011 * in the Software without restriction, including without limitation the rights 012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 013 * copies of the Software, and to permit persons to whom the Software is 014 * furnished to do so, subject to the following conditions: 015 * 016 * The above copyright notice and this permission notice shall be included in 017 * all copies or substantial portions of the Software. 018 * 019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 025 * THE SOFTWARE. 026 * #L% 027 */ 028 029import com.github.theholywaffle.teamspeak3.api.wrapper.FileTransferParameters; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.OutputStream; 036import java.net.Socket; 037import java.util.concurrent.atomic.AtomicInteger; 038import java.util.zip.CRC32; 039 040public class FileTransferHelper { 041 042 private static final Logger log = LoggerFactory.getLogger(FileTransferHelper.class); 043 private static final int BUFFER_SIZE = 16_384; // 16 kB 044 045 // Can only be in the range 0 - 65535 046 private final AtomicInteger clientTransferId = new AtomicInteger(0); 047 private final String defaultHost; 048 049 FileTransferHelper(String host) { 050 defaultHost = host; 051 } 052 053 // FILES 054 055 public void downloadFile(OutputStream dataOut, FileTransferParameters params) throws IOException { 056 final String host = getHostFromResponse(params.getFileServerHost()); 057 final int port = params.getFileServerPort(); 058 final long dataLength = params.getFileSize(); 059 final int downloadId = params.getClientTransferId() + 1; 060 061 log.info("[Download {}] Download started", downloadId); 062 try (Socket socket = new Socket(host, port)) { 063 socket.setReceiveBufferSize(BUFFER_SIZE); 064 int actualSize = socket.getReceiveBufferSize(); 065 066 OutputStream out = socket.getOutputStream(); 067 out.write(params.getFileTransferKey().getBytes("UTF-8")); 068 out.flush(); 069 070 InputStream in = socket.getInputStream(); 071 byte[] buffer = new byte[actualSize]; 072 long total = 0; 073 while (total < dataLength) { 074 int read = in.read(buffer); 075 if (read < 0) throw new IOException("Server response contained less data than specified"); 076 total += read; 077 if (total > dataLength) throw new IOException("Server response contained more data than specified"); 078 dataOut.write(buffer, 0, read); 079 } 080 log.info("[Download {}] Download finished", downloadId); 081 } catch (IOException e) { 082 // Log and re-throw 083 log.warn("[Download {}] Download failed: {}", downloadId, e.getMessage()); 084 throw e; 085 } 086 } 087 088 public void uploadFile(InputStream dataIn, long dataLength, FileTransferParameters params) throws IOException { 089 final String host = getHostFromResponse(params.getFileServerHost()); 090 final int port = params.getFileServerPort(); 091 final int uploadId = params.getClientTransferId() + 1; 092 093 log.info("[Upload {}] Upload started", uploadId); 094 try (Socket socket = new Socket(host, port)) { 095 socket.setSendBufferSize(BUFFER_SIZE); 096 int actualSize = socket.getSendBufferSize(); 097 098 OutputStream out = socket.getOutputStream(); 099 out.write(params.getFileTransferKey().getBytes("UTF-8")); 100 out.flush(); 101 102 byte[] buffer = new byte[actualSize]; 103 long total = 0; 104 while (total < dataLength) { 105 int toRead = (int) Math.min(actualSize, dataLength - total); 106 int read = dataIn.read(buffer, 0, toRead); 107 if (read < 0) throw new IOException("User stream did not contain enough data"); 108 total += read; 109 out.write(buffer, 0, read); 110 } 111 log.info("[Upload {}] Upload finished", uploadId); 112 } catch (IOException e) { 113 // Log and re-throw 114 log.warn("[Upload {}] Upload failed: {}", uploadId, e.getMessage()); 115 throw e; 116 } 117 } 118 119 // ICONS 120 121 public long getIconId(byte[] data) { 122 final CRC32 crc32 = new CRC32(); 123 crc32.update(data); 124 return crc32.getValue(); 125 } 126 127 // UTIL 128 129 public byte[] readFully(InputStream dataIn, long dataLength) throws IOException { 130 if (dataLength > Integer.MAX_VALUE - 64) throw new IOException("File too large"); 131 132 final int len = (int) dataLength; 133 byte[] data = new byte[len]; 134 int total = 0; 135 while (total < len) { 136 int read = dataIn.read(data, total, len - total); 137 if (read < 0) throw new IOException("User stream did not contain enough data"); 138 total += read; 139 } 140 return data; 141 } 142 143 public int getClientTransferId() { 144 // AtomicInteger#getAndUpdate without Java 8 145 int prev, next; 146 do { 147 prev = clientTransferId.get(); 148 next = (prev + 1) & 0xFFFF; 149 } while (!clientTransferId.compareAndSet(prev, next)); 150 return prev; 151 } 152 153 private String getHostFromResponse(String raw) { 154 if (raw == null || raw.isEmpty()) return defaultHost; 155 if (raw.startsWith("0.0.0.0")) return defaultHost; 156 int firstComma = raw.indexOf(','); 157 if (firstComma <= 0) return defaultHost; 158 return raw.substring(0, firstComma); 159 } 160}