Làm cách nào để truy xuất tệp từ máy chủ qua SFTP?


228

Tôi đang cố gắng truy xuất tệp từ máy chủ bằng SFTP (trái ngược với FTPS) bằng Java. Tôi có thể làm cái này như thế nào?

Câu trả lời:


198

Một lựa chọn khác là xem xét việc xem xét thư viện JSch . JSch dường như là thư viện ưa thích cho một vài dự án nguồn mở lớn, bao gồm cả Eclipse, Ant và Apache Commons HttpClient, trong số những dự án khác.

Nó hỗ trợ cả đăng nhập người dùng / vượt qua và dựa trên chứng chỉ, cũng như toàn bộ một loạt các tính năng SSH2 tuyệt vời khác.

Đây là một tập tin từ xa đơn giản lấy qua SFTP. Xử lý lỗi được để lại như một bài tập cho người đọc :-)

JSch jsch = new JSch();

String knownHostsFilename = "/home/username/.ssh/known_hosts";
jsch.setKnownHosts( knownHostsFilename );

Session session = jsch.getSession( "remote-username", "remote-host" );    
{
  // "interactive" version
  // can selectively update specified known_hosts file 
  // need to implement UserInfo interface
  // MyUserInfo is a swing implementation provided in 
  //  examples/Sftp.java in the JSch dist
  UserInfo ui = new MyUserInfo();
  session.setUserInfo(ui);

  // OR non-interactive version. Relies in host key being in known-hosts file
  session.setPassword( "remote-password" );
}

session.connect();

Channel channel = session.openChannel( "sftp" );
channel.connect();

ChannelSftp sftpChannel = (ChannelSftp) channel;

sftpChannel.get("remote-file", "local-file" );
// OR
InputStream in = sftpChannel.get( "remote-file" );
  // process inputstream as needed

sftpChannel.exit();
session.disconnect();

1
Cheekysoft, tôi nhận thấy - trong khi sử dụng Jsch - xóa các tệp trên máy chủ sftp không hoạt động. Ngoài ra đổi tên tập tin không hoạt động quá. Có ý kiến ​​nào không ??? Andy

1
Xin lỗi, nó không phải là thứ tôi làm việc vào lúc này. (Vui lòng thử và để lại các loại phản hồi này dưới dạng nhận xét - như tin nhắn này - và không phải là câu trả lời mới cho câu hỏi ban đầu)
Cheekysoft

1
Khối mã đó sau khi gán phiên là gì? Đó có phải là một số cú pháp Java ưa thích mà tôi chưa từng thấy? Nếu vậy - những gì nó hoàn thành được viết theo cách đó?
Michael Peterson

3
Cú pháp java tiêu chuẩn @ p1x3l5 cho phép một khối được chèn vào bất cứ đâu; nó có thể được sử dụng để cung cấp sự kiểm soát tốt hơn đối với phạm vi biến, nếu bạn muốn. Tuy nhiên, trong trường hợp này, nó chỉ là một trợ giúp trực quan để giúp chỉ ra hai lựa chọn thực hiện: sử dụng phiên bản tương tác yêu cầu mật khẩu từ người dùng hoặc sử dụng mật khẩu mã hóa không yêu cầu người dùng can thiệp nhưng có thể có rủi ro bảo mật bổ sung.
Cheekysoft

109

Dưới đây là mã nguồn hoàn chỉnh của một ví dụ sử dụng JSch mà không phải lo lắng về việc kiểm tra khóa ssh.

import com.jcraft.jsch.*;

public class TestJSch {
    public static void main(String args[]) {
        JSch jsch = new JSch();
        Session session = null;
        try {
            session = jsch.getSession("username", "127.0.0.1", 22);
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword("password");
            session.connect();

            Channel channel = session.openChannel("sftp");
            channel.connect();
            ChannelSftp sftpChannel = (ChannelSftp) channel;
            sftpChannel.get("remotefile.txt", "localfile.txt");
            sftpChannel.exit();
            session.disconnect();
        } catch (JSchException e) {
            e.printStackTrace();  
        } catch (SftpException e) {
            e.printStackTrace();
        }
    }
}

15
Một finallykhối nên được sử dụng để bao gồm mã dọn dẹp kênh, để đảm bảo rằng nó luôn chạy.
hotshot309

Bây giờ tôi đang nhận được ngoại lệ này: com.jcraft.jsch.JSchException: Session.connect: java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 2048 (inclusive)
anon58192932

Tôi thấy JSCH có thêm 0 hoặc 1 phụ thuộc. Bạn có thể bỏ qua phụ thuộc JZLIB nếu bạn tắt tính năng nén. // tắt nén session.setConfig ("compression.s2c", "none"); session.setConfig ("compression.c2s", "none");
englebart

1
Nếu không kiểm tra máy chủ nghiêm ngặt, bạn sẽ dễ bị tấn công trung gian.
rustyx

44

Dưới đây là một ví dụ sử dụng VFS chung của Apache:

FileSystemOptions fsOptions = new FileSystemOptions();
SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(fsOptions, "no");
FileSystemManager fsManager = VFS.getManager();
String uri = "sftp://user:password@host:port/absolute-path";
FileObject fo = fsManager.resolveFile(uri, fsOptions);

5
Một điều thú vị khác là đặt thời gian chờ, để nếu hệ thống từ xa ngoại tuyến, bạn sẽ không bị treo ở đó mãi mãi. Bạn có thể làm điều này giống như đã được thực hiện để vô hiệu hóa kiểm tra khóa máy chủ: SftpFileSystemConfigBuilder.getInstance (). SetTimeout (fsOptions, 5000);
Scott Jones

Làm thế nào bạn có thể khuyên đóng kết nối này khi sử dụng nhiều máy khách SFTP cùng một lúc?
2Big2BeSall vào

2
Nếu mật khẩu của tôi chứa ký hiệu @ thì sao?
dùng3

23

Đây là giải pháp tôi đã đưa ra với http://sourceforge.net/projects/sshtools/ (hầu hết xử lý lỗi được bỏ qua cho rõ ràng). Đây là một đoạn trích từ blog của tôi

SshClient ssh = new SshClient();
ssh.connect(host, port);
//Authenticate
PasswordAuthenticationClient passwordAuthenticationClient = new PasswordAuthenticationClient();
passwordAuthenticationClient.setUsername(userName);
passwordAuthenticationClient.setPassword(password);
int result = ssh.authenticate(passwordAuthenticationClient);
if(result != AuthenticationProtocolState.COMPLETE){
     throw new SFTPException("Login to " + host + ":" + port + " " + userName + "/" + password + " failed");
}
//Open the SFTP channel
SftpClient client = ssh.openSftpClient();
//Send the file
client.put(filePath);
//disconnect
client.quit();
ssh.disconnect();

7
Tôi đồng ý (muộn màng), nó hoạt động tốt cho trang web gốc / tải xuống mà tôi yêu cầu nhưng nó đã từ chối hoạt động cho trang mới. Tôi đang trong quá trình chuyển sang JSch
David Hayes

23

Một bản tóm tắt hay trên Jsch là Apache commons-vfs cung cấp API hệ thống tệp ảo giúp truy cập và ghi các tệp SFTP gần như trong suốt. Làm việc tốt cho chúng tôi.


1
có thể sử dụng các khóa chia sẻ trước kết hợp với commons-vfs không?
Benedikt Waldvogel

2
Vâng, đúng vậy. Nếu bạn cần danh tính không chuẩn, bạn có thể gọi SftpFileSystemConfigBuilder.getInstance (). SetIdentities (...).
Russ Hayward

Bạn có thể sử dụng các khóa chia sẻ trước. Nhưng khóa này phải không có mật khẩu. OtrosLogViewer đang sử dụng ủy quyền khóa SSH với VFS nhưng yêu cầu xóa cụm mật khẩu khỏi khóa ( code.google.com/p/otroslogviewer/wiki/SftpAuthPubKey )
KrzyH

19

Có một so sánh khá hay về 3 thư viện Java trưởng thành cho SFTP: Commons VFS, SSHJ và JSch

Tóm lại, SSHJ có API rõ ràng nhất và đó là API tốt nhất nếu bạn không cần hỗ trợ lưu trữ khác do Commons VFS cung cấp.

Dưới đây là ví dụ SSHJ được chỉnh sửa từ github :

final SSHClient ssh = new SSHClient();
ssh.loadKnownHosts(); // or, to skip host verification: ssh.addHostKeyVerifier(new PromiscuousVerifier())
ssh.connect("localhost");
try {
    ssh.authPassword("user", "password"); // or ssh.authPublickey(System.getProperty("user.name"))
    final SFTPClient sftp = ssh.newSFTPClient();
    try {
        sftp.get("test_file", "/tmp/test.tmp");
    } finally {
        sftp.close();
    }
} finally {
    ssh.disconnect();
}

2
Có cách nào để lấy tệp dưới dạng InputStream không?
Johan

2
sshj năm 2019 vẫn được duy trì tốt và được sử dụng bởi dự án Alpakka (Akka)
Maxence

13

Thư viện SFTP của Apache Commons

Tệp thuộc tính java phổ biến cho tất cả các ví dụ

Máy chủĐịa chỉ = 111.222.333.444

userId = myUserId

mật khẩu = myPassword

remoteDirectory = sản phẩm /

localDirectory = nhập /

Tải tệp lên máy chủ từ xa bằng SFTP

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.Selectors;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

public class SendMyFiles {

 static Properties props;

 public static void main(String[] args) {

  SendMyFiles sendMyFiles = new SendMyFiles();
  if (args.length < 1)
  {
   System.err.println("Usage: java " + sendMyFiles.getClass().getName()+
     " Properties_file File_To_FTP ");
   System.exit(1);
  }

  String propertiesFile = args[0].trim();
  String fileToFTP = args[1].trim();
  sendMyFiles.startFTP(propertiesFile, fileToFTP);

 }

 public boolean startFTP(String propertiesFilename, String fileToFTP){

  props = new Properties();
  StandardFileSystemManager manager = new StandardFileSystemManager();

  try {

   props.load(new FileInputStream("properties/" + propertiesFilename));
   String serverAddress = props.getProperty("serverAddress").trim();
   String userId = props.getProperty("userId").trim();
   String password = props.getProperty("password").trim();
   String remoteDirectory = props.getProperty("remoteDirectory").trim();
   String localDirectory = props.getProperty("localDirectory").trim();

   //check if the file exists
   String filepath = localDirectory +  fileToFTP;
   File file = new File(filepath);
   if (!file.exists())
    throw new RuntimeException("Error. Local file not found");

   //Initializes the file manager
   manager.init();

   //Setup our SFTP configuration
   FileSystemOptions opts = new FileSystemOptions();
   SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
     opts, "no");
   SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);
   SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

   //Create the SFTP URI using the host name, userid, password,  remote path and file name
   String sftpUri = "sftp://" + userId + ":" + password +  "@" + serverAddress + "/" + 
     remoteDirectory + fileToFTP;

   // Create local file object
   FileObject localFile = manager.resolveFile(file.getAbsolutePath());

   // Create remote file object
   FileObject remoteFile = manager.resolveFile(sftpUri, opts);

   // Copy local file to sftp server
   remoteFile.copyFrom(localFile, Selectors.SELECT_SELF);
   System.out.println("File upload successful");

  }
  catch (Exception ex) {
   ex.printStackTrace();
   return false;
  }
  finally {
   manager.close();
  }

  return true;
 }


}

Tải xuống tệp từ máy chủ từ xa bằng SFTP

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.Selectors;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

public class GetMyFiles {

 static Properties props;

 public static void main(String[] args) {

  GetMyFiles getMyFiles = new GetMyFiles();
  if (args.length < 1)
  {
   System.err.println("Usage: java " + getMyFiles.getClass().getName()+
   " Properties_filename File_To_Download ");
   System.exit(1);
  }

  String propertiesFilename = args[0].trim();
  String fileToDownload = args[1].trim();
  getMyFiles.startFTP(propertiesFilename, fileToDownload);

 }

 public boolean startFTP(String propertiesFilename, String fileToDownload){

  props = new Properties();
  StandardFileSystemManager manager = new StandardFileSystemManager();

  try {

   props.load(new FileInputStream("properties/" + propertiesFilename));
   String serverAddress = props.getProperty("serverAddress").trim();
   String userId = props.getProperty("userId").trim();
   String password = props.getProperty("password").trim();
   String remoteDirectory = props.getProperty("remoteDirectory").trim();
   String localDirectory = props.getProperty("localDirectory").trim();


   //Initializes the file manager
   manager.init();

   //Setup our SFTP configuration
   FileSystemOptions opts = new FileSystemOptions();
   SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
     opts, "no");
   SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);
   SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

   //Create the SFTP URI using the host name, userid, password,  remote path and file name
   String sftpUri = "sftp://" + userId + ":" + password +  "@" + serverAddress + "/" + 
     remoteDirectory + fileToDownload;

   // Create local file object
   String filepath = localDirectory +  fileToDownload;
   File file = new File(filepath);
   FileObject localFile = manager.resolveFile(file.getAbsolutePath());

   // Create remote file object
   FileObject remoteFile = manager.resolveFile(sftpUri, opts);

   // Copy local file to sftp server
   localFile.copyFrom(remoteFile, Selectors.SELECT_SELF);
   System.out.println("File download successful");

  }
  catch (Exception ex) {
   ex.printStackTrace();
   return false;
  }
  finally {
   manager.close();
  }

  return true;
 }

}

Xóa tệp trên máy chủ từ xa bằng SFTP

import java.io.FileInputStream;
import java.util.Properties;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

public class DeleteRemoteFile {

 static Properties props;

 public static void main(String[] args) {

  DeleteRemoteFile getMyFiles = new DeleteRemoteFile();
  if (args.length < 1)
  {
   System.err.println("Usage: java " + getMyFiles.getClass().getName()+
   " Properties_filename File_To_Delete ");
   System.exit(1);
  }

  String propertiesFilename = args[0].trim();
  String fileToDownload = args[1].trim();
  getMyFiles.startFTP(propertiesFilename, fileToDownload);

 }

 public boolean startFTP(String propertiesFilename, String fileToDownload){

  props = new Properties();
  StandardFileSystemManager manager = new StandardFileSystemManager();

  try {

   props.load(new FileInputStream("properties/" + propertiesFilename));
   String serverAddress = props.getProperty("serverAddress").trim();
   String userId = props.getProperty("userId").trim();
   String password = props.getProperty("password").trim();
   String remoteDirectory = props.getProperty("remoteDirectory").trim();


   //Initializes the file manager
   manager.init();

   //Setup our SFTP configuration
   FileSystemOptions opts = new FileSystemOptions();
   SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
     opts, "no");
   SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);
   SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

   //Create the SFTP URI using the host name, userid, password,  remote path and file name
   String sftpUri = "sftp://" + userId + ":" + password +  "@" + serverAddress + "/" + 
     remoteDirectory + fileToDownload;

   //Create remote file object
   FileObject remoteFile = manager.resolveFile(sftpUri, opts);

   //Check if the file exists
   if(remoteFile.exists()){
    remoteFile.delete();
    System.out.println("File delete successful");
   }

  }
  catch (Exception ex) {
   ex.printStackTrace();
   return false;
  }
  finally {
   manager.close();
  }

  return true;
 }

}


Cách cấu hình trong khi có ssh-key (khóa chung) để sao chép tệp trên máy chủ. Bởi vì tôi cần tạo ssh_trust giữa máy chủ của tôi và máy chủ từ xa.
MS Parmar

7

hierynomus / sshj đã triển khai hoàn chỉnh SFTP phiên bản 3 (những gì OpenSSH thực hiện)

Mã ví dụ từ SFTPUpload.java

package net.schmizz.sshj.examples;

import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.xfer.FileSystemFile;

import java.io.File;
import java.io.IOException;

/** This example demonstrates uploading of a file over SFTP to the SSH server. */
public class SFTPUpload {

    public static void main(String[] args)
            throws IOException {
        final SSHClient ssh = new SSHClient();
        ssh.loadKnownHosts();
        ssh.connect("localhost");
        try {
            ssh.authPublickey(System.getProperty("user.name"));
            final String src = System.getProperty("user.home") + File.separator + "test_file";
            final SFTPClient sftp = ssh.newSFTPClient();
            try {
                sftp.put(new FileSystemFile(src), "/tmp");
            } finally {
                sftp.close();
            }
        } finally {
            ssh.disconnect();
        }
    }

}

2
công việc tốt!! một ví dụ trong trang chính có thể hữu ích mặc dù.
OhadR

4

Thư viện JSch là thư viện mạnh mẽ có thể được sử dụng để đọc tệp từ máy chủ SFTP. Dưới đây là mã được kiểm tra để đọc tệp từ dòng vị trí SFTP theo dòng

JSch jsch = new JSch();
        Session session = null;
        try {
            session = jsch.getSession("user", "127.0.0.1", 22);
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword("password");
            session.connect();

            Channel channel = session.openChannel("sftp");
            channel.connect();
            ChannelSftp sftpChannel = (ChannelSftp) channel;

            InputStream stream = sftpChannel.get("/usr/home/testfile.txt");
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(stream));
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }

            } catch (IOException io) {
                System.out.println("Exception occurred during reading file from SFTP server due to " + io.getMessage());
                io.getMessage();

            } catch (Exception e) {
                System.out.println("Exception occurred during reading file from SFTP server due to " + e.getMessage());
                e.getMessage();

            }

            sftpChannel.exit();
            session.disconnect();
        } catch (JSchException e) {
            e.printStackTrace();
        } catch (SftpException e) {
            e.printStackTrace();
        }

Vui lòng tham khảo blog cho toàn bộ chương trình.


3

Andy, để xóa tệp trên hệ thống từ xa, bạn cần sử dụng (channelExec)JSch và truyền các lệnh unix / linux để xóa nó.


2

Hãy thử edtFTPj / PRO , một thư viện máy khách SFTP trưởng thành, mạnh mẽ hỗ trợ các nhóm kết nối và các hoạt động không đồng bộ. Cũng hỗ trợ FTP và FTPS để tất cả các cơ sở để truyền tệp an toàn được bảo hiểm.



2

Mặc dù các câu trả lời ở trên rất hữu ích, tôi đã dành một ngày để làm cho chúng hoạt động, đối mặt với các ngoại lệ khác nhau như "kênh bị hỏng", "khóa rsa không xác định" và "gói bị hỏng".

Dưới đây là một lớp có thể sử dụng lại làm việc cho SFTP FILES UPLOAD / DOWNLOAD bằng thư viện JSch.

Tải lên sử dụng:

SFTPFileCopy upload = new SFTPFileCopy(true, /path/to/sourcefile.png", /path/to/destinationfile.png");

Tải về sử dụng:

SFTPFileCopy download = new SFTPFileCopy(false, "/path/to/sourcefile.png", "/path/to/destinationfile.png");

Mã lớp:

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.swing.JOptionPane;
import menue.Menue;

public class SFTPFileCopy1 {

    public SFTPFileCopy1(boolean upload, String sourcePath, String destPath) throws FileNotFoundException, IOException {
        Session session = null;
        Channel channel = null;
        ChannelSftp sftpChannel = null;
        try {
            JSch jsch = new JSch();
            //jsch.setKnownHosts("/home/user/.putty/sshhostkeys");
            session = jsch.getSession("login", "mysite.com", 22);
            session.setPassword("password");

            UserInfo ui = new MyUserInfo() {
                public void showMessage(String message) {

                    JOptionPane.showMessageDialog(null, message);

                }

                public boolean promptYesNo(String message) {

                    Object[] options = {"yes", "no"};

                    int foo = JOptionPane.showOptionDialog(null,
                            message,
                            "Warning",
                            JOptionPane.DEFAULT_OPTION,
                            JOptionPane.WARNING_MESSAGE,
                            null, options, options[0]);

                    return foo == 0;

                }
            };
            session.setUserInfo(ui);

            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();
            channel = session.openChannel("sftp");
            channel.setInputStream(System.in);
            channel.setOutputStream(System.out);
            channel.connect();
            sftpChannel = (ChannelSftp) channel;

            if (upload) { // File upload.
                byte[] bufr = new byte[(int) new File(sourcePath).length()];
                FileInputStream fis = new FileInputStream(new File(sourcePath));
                fis.read(bufr);
                ByteArrayInputStream fileStream = new ByteArrayInputStream(bufr);
                sftpChannel.put(fileStream, destPath);
                fileStream.close();
            } else { // File download.
                byte[] buffer = new byte[1024];
                BufferedInputStream bis = new BufferedInputStream(sftpChannel.get(sourcePath));
                OutputStream os = new FileOutputStream(new File(destPath));
                BufferedOutputStream bos = new BufferedOutputStream(os);
                int readCount;
                while ((readCount = bis.read(buffer)) > 0) {
                    bos.write(buffer, 0, readCount);
                }
                bis.close();
                bos.close();
            }
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            if (sftpChannel != null) {
                sftpChannel.exit();
            }
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }

    public static abstract class MyUserInfo
            implements UserInfo, UIKeyboardInteractive {

        public String getPassword() {
            return null;
        }

        public boolean promptYesNo(String str) {
            return false;
        }

        public String getPassphrase() {
            return null;
        }

        public boolean promptPassphrase(String message) {
            return false;
        }

        public boolean promptPassword(String message) {
            return false;
        }

        public void showMessage(String message) {
        }

        public String[] promptKeyboardInteractive(String destination,
                String name,
                String instruction,
                String[] prompt,
                boolean[] echo) {

            return null;
        }
    }
}


1

Tôi sử dụng API SFTP này được gọi là Zehon, thật tuyệt vời, rất dễ sử dụng với nhiều mã mẫu. Đây là trang web http://www.zehon.com


2
Zehon dường như đã chết. Và nguồn ở đâu? 'Giấy phép' nào đứng sau 'miễn phí'?
rü- 6/10/2015

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.