溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java安全編碼指南之文件IO操作的方法是什么

發(fā)布時間:2021-10-25 17:15:30 來源:億速云 閱讀:112 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“java安全編碼指南之文件IO操作的方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“java安全編碼指南之文件IO操作的方法是什么”吧!

創(chuàng)建文件的時候指定合適的權(quán)限

不管是在windows還是linux,文件都有權(quán)限控制的概念,我們可以設(shè)置文件的owner,還有文件的permission,如果文件權(quán)限沒有控制好的話,惡意用戶就有可能對我們的文件進(jìn)行惡意操作。

所以我們在文件創(chuàng)建的時候就需要考慮到權(quán)限的問題。

很遺憾的是,java并不是以文件操作見長的,所以在JDK1.6之前,java的IO操作是非常弱的,基本的文件操作類,比如FileOutputStream和FileWriter并沒有權(quán)限的選項(xiàng)。

Writer out = new FileWriter("file");

那么怎么處理呢?

在JDK1.6之前,我們需要借助于一些本地方法來實(shí)現(xiàn)權(quán)限的修改功能。

在JDK1.6之后,java引入了NIO,可以通過NIO的一些特性來控制文件的權(quán)限功能。

我們看一下Files工具類的createFile方法:

    public static Path createFile(Path path, FileAttribute<?>... attrs)
       throws IOException
   {
       newByteChannel(path, DEFAULT_CREATE_OPTIONS, attrs).close();
       return path;
   }

其中FileAttribute就是文件的屬性,我們看一下怎么指定文件的權(quán)限:

    public void createFileWithPermission() throws IOException {
       Set<PosixFilePermission> perms =
               PosixFilePermissions.fromString("rw-------");
       FileAttribute<Set<PosixFilePermission>> attr =
               PosixFilePermissions.asFileAttribute(perms);
       Path file = new File("/tmp/www.flydean.com").toPath();
       Files.createFile(file,attr);
   }

注意檢查文件操作的返回值

java中很多文件操作是有返回值的,比如file.delete(),我們需要根據(jù)返回值來判斷文件操作是否完成,所以不要忽略了返回值。

刪除使用過后的臨時文件

如果我們使用到不需要永久存儲的文件時,就可以很方便的使用File的createTempFile來創(chuàng)建臨時文件。臨時文件的名字是隨機(jī)生成的,我們希望在臨時文件使用完畢之后將其刪除。

怎么刪除呢?File提供了一個deleteOnExit方法,這個方法會在JVM退出的時候?qū)⑽募h除。

注意,這里的JVM一定要是正常退出的,如果是非正常退出,文件不會被刪除。

我們看下面的例子:

    public void wrongDelete() throws IOException {
       File f = File.createTempFile("tmpfile",".tmp");
       FileOutputStream fop = null;
       try {
           fop = new FileOutputStream(f);
           String str = "Data";
           fop.write(str.getBytes());
           fop.flush();
       } finally {
           // 因?yàn)镾tream沒有被關(guān)閉,所以文件在windows平臺上面不會被刪除
           f.deleteOnExit(); // 在JVM退出的時候刪除臨時文件

           if (fop != null) {
               try {
                   fop.close();
               } catch (IOException x) {
                   // Handle error
               }
           }
       }
   }

上面的例子中,我們創(chuàng)建了一個臨時文件,并且在finally中調(diào)用了deleteOnExit方法,但是因?yàn)樵谡{(diào)用該方法的時候,Stream并沒有關(guān)閉,所以在windows平臺上會出現(xiàn)文件沒有被刪除的情況。

怎么解決呢?

NIO提供了一個DELETE_ON_CLOSE選項(xiàng),可以保證文件在關(guān)閉之后就被刪除:

    public void correctDelete() throws IOException {
       Path tempFile = null;
           tempFile = Files.createTempFile("tmpfile", ".tmp");
           try (BufferedWriter writer =
                        Files.newBufferedWriter(tempFile, Charset.forName("UTF8"),
                                StandardOpenOption.DELETE_ON_CLOSE)) {
               // Write to file
           }
       }

上面的例子中,我們在writer的創(chuàng)建過程中加入了StandardOpenOption.DELETE_ON_CLOSE,那么文件將會在writer關(guān)閉之后被刪除。

釋放不再被使用的資源

如果資源不再被使用了,我們需要記得關(guān)閉他們,否則就會造成資源的泄露。

但是很多時候我們可能會忘記關(guān)閉,那么該怎么辦呢?JDK7中引入了try-with-resources機(jī)制,只要把實(shí)現(xiàn)了Closeable接口的資源放在try語句中就會自動被關(guān)閉,很方便。

注意Buffer的安全性

NIO中提供了很多非常有用的Buffer類,比如IntBuffer, CharBuffer 和 ByteBuffer等,這些Buffer實(shí)際上是對底層的數(shù)組的封裝,雖然創(chuàng)建了新的Buffer對象,但是這個Buffer是和底層的數(shù)組相關(guān)聯(lián)的,所以不要輕易的將Buffer暴露出去,否則可能會修改底層的數(shù)組。

    public CharBuffer getBuffer(){
        char[] dataArray = new char[10];
        return CharBuffer.wrap(dataArray);
   }

上面的例子暴露了CharBuffer,實(shí)際上也暴露了底層的char數(shù)組。

有兩種方式對其進(jìn)行改進(jìn):

    public CharBuffer getBuffer1(){
       char[] dataArray = new char[10];
       return CharBuffer.wrap(dataArray).asReadOnlyBuffer();
   }

第一種方式就是將CharBuffer轉(zhuǎn)換成為只讀的。

第二種方式就是創(chuàng)建一個新的Buffer,切斷Buffer和數(shù)組的聯(lián)系:

    public CharBuffer getBuffer2(){
       char[] dataArray = new char[10];
       CharBuffer cb = CharBuffer.allocate(dataArray.length);
       cb.put(dataArray);
       return cb;
   }

注意 Process 的標(biāo)準(zhǔn)輸入輸出

java中可以通過Runtime.exec()來執(zhí)行native的命令,而Runtime.exec()是有返回值的,它的返回值是一個Process對象,用來控制和獲取native程序的執(zhí)行信息。

默認(rèn)情況下,創(chuàng)建出來的Process是沒有自己的I/O stream的,這就意味著Process使用的是父process的I/O(stdin, stdout, stderr),Process提供了下面的三種方法來獲取I/O:

getOutputStream()
getInputStream()
getErrorStream()

如果是使用parent process的IO,那么在有些系統(tǒng)上面,這些buffer空間比較小,如果出現(xiàn)大量輸入輸出操作的話,就有可能被阻塞,甚至是死鎖。

怎么辦呢?我們要做的就是將Process產(chǎn)生的IO進(jìn)行處理,以防止Buffer的阻塞。

public class StreamProcesser implements Runnable{
   private final InputStream is;
   private final PrintStream os;

   StreamProcesser(InputStream is, PrintStream os){
       this.is=is;
       this.os=os;
   }

   @Override
   public void run() {
       try {
           int c;
           while ((c = is.read()) != -1)
               os.print((char) c);
       } catch (IOException x) {
           // Handle error
       }
   }

   public static void main(String[] args) throws IOException, InterruptedException {
       Runtime rt = Runtime.getRuntime();
       Process proc = rt.exec("vscode");

       Thread errorGobbler
               = new Thread(new StreamProcesser(proc.getErrorStream(), System.err));

       Thread outputGobbler
               = new Thread(new StreamProcesser(proc.getInputStream(), System.out));

       errorGobbler.start();
       outputGobbler.start();

       int exitVal = proc.waitFor();
       errorGobbler.join();
       outputGobbler.join();
   }
}

上面的例子中,我們創(chuàng)建了一個StreamProcesser來處理Process的Error和Input。

InputStream.read() 和 Reader.read()

InputStream和Reader都有一個read()方法,這兩個方法的不同之處就是InputStream read的是Byte,而Reader read的是char。

雖然Byte的范圍是-128到127,但是InputStream.read()會將讀取到的Byte轉(zhuǎn)換成0-255(0x00-0xff)范圍的int。

Char的范圍是0x0000-0xffff,Reader.read()將會返回同樣范圍的int值:0x0000-0xffff。

如果返回值是-1,表示的是Stream結(jié)束了。這里-1的int表示是:0xffffffff。

我們在使用的過程中,需要對讀取的返回值進(jìn)行判斷,以用來區(qū)分Stream的邊界。

我們考慮這樣的一個問題:

FileInputStream in;
byte data;
while ((data = (byte) in.read()) != -1) {
}

上面我們將InputStream的read結(jié)果先進(jìn)行byte的轉(zhuǎn)換,然后再判斷是否等于-1。會有什么問題呢?

如果Byte本身的值是0xff,本身是一個-1,但是InputStream在讀取之后,將其轉(zhuǎn)換成為0-255范圍的int,那么轉(zhuǎn)換之后的int值是:0x000000FF, 再次進(jìn)行byte轉(zhuǎn)換,將會截取最后的Oxff, Oxff == -1,最終導(dǎo)致錯誤的判斷Stream結(jié)束。

所以我們需要先做返回值的判斷,然后再進(jìn)行轉(zhuǎn)換:

FileInputStream in;
int inbuff;
byte data;
while ((inbuff = in.read()) != -1) {
 data = (byte) inbuff;
 // ...
}

拓展閱讀:

這段代碼的輸出結(jié)果是多少呢?(int)(char)(byte)-1

首先-1轉(zhuǎn)換成為byte:-1是0xffffffff,轉(zhuǎn)換成為byte直接截取最后幾位,得到0xff,也就是-1.

然后byte轉(zhuǎn)換成為char:0xff byte是有符號的,轉(zhuǎn)換成為2個字節(jié)的char需要進(jìn)行符號位擴(kuò)展,變成0xffff,但是char是無符號的,對應(yīng)的十進(jìn)制是65535。

最后char轉(zhuǎn)換成為int,因?yàn)閏har是無符號的,所以擴(kuò)展成為0x0000ffff,對應(yīng)的十進(jìn)制數(shù)是65535.

同樣的下面的例子中,如果提前使用char對int進(jìn)行轉(zhuǎn)換,因?yàn)閏har的范圍是無符號的,所以永遠(yuǎn)不可能等于-1.

FileReader in;
char data;
while ((data = (char) in.read()) != -1) {
 // ...
}

write() 方法不要超出范圍

在OutputStream中有一個很奇怪的方法,就是write,我們看下write方法的定義:

    public abstract void write(int b) throws IOException;

write接收一個int參數(shù),但是實(shí)際上寫入的是一個byte。

因?yàn)閕nt和byte的范圍不一樣,所以傳入的int將會被截取最后的8位來轉(zhuǎn)換成一個byte。

所以我們在使用的時候一定要判斷寫入的范圍:

    public void writeInt(int value){
       int intValue = Integer.valueOf(value);
       if (intValue < 0 || intValue > 255) {
           throw new ArithmeticException("Value超出范圍");
       }
       System.out.write(value);
       System.out.flush();
   }

或者有些Stream操作是可以直接writeInt的,我們可以直接調(diào)用。

注意帶數(shù)組的read的使用

InputStream有兩種帶數(shù)組的read方法:

public int read(byte b[]) throws IOException

public int read(byte b[], int off, int len) throws IOException

如果我們使用了這兩種方法,那么一定要注意讀取到的byte數(shù)組是否被填滿,考慮下面的一個例子:

    public String wrongRead(InputStream in) throws IOException {
       byte[] data = new byte[1024];
       if (in.read(data) == -1) {
           throw new EOFException();
       }
       return new String(data, "UTF-8");
   }

如果InputStream的數(shù)據(jù)并沒有1024,或者說因?yàn)榫W(wǎng)絡(luò)的原因并沒有將1024填充滿,那么我們將會得到一個沒有填充滿的數(shù)組,那么我們使用起來其實(shí)是有問題的。

怎么正確的使用呢?

    public String readArray(InputStream in) throws IOException {
       int offset = 0;
       int bytesRead = 0;
       byte[] data = new byte[1024];
       while ((bytesRead = in.read(data, offset, data.length - offset))
               != -1) {
           offset += bytesRead;
           if (offset >= data.length) {
               break;
           }
       }
       String str = new String(data, 0, offset, "UTF-8");
       return str;
   }

我們需要記錄實(shí)際讀取的byte數(shù)目,通過記載偏移量,我們得到了最終實(shí)際讀取的結(jié)果。

或者我們可以使用DataInputStream的readFully方法,保證讀取完整的byte數(shù)組。

little-endian和big-endian的問題

java中的數(shù)據(jù)默認(rèn)是以big-endian的方式來存儲的,DataInputStream中的readByte(), readShort(), readInt(), readLong(), readFloat(), 和 readDouble()默認(rèn)也是以big-endian來讀取數(shù)據(jù)的,如果在和其他的以little-endian進(jìn)行交互的過程中,就可能出現(xiàn)問題。

我們需要的是將little-endian轉(zhuǎn)換成為big-endian。

怎么轉(zhuǎn)換呢?

比如,我們想要讀取一個int,可以首先使用read方法讀取4個字節(jié),然后再對讀取的4個字節(jié)做little-endian到big-endian的轉(zhuǎn)換。

    public void method1(InputStream inputStream) throws IOException {
       try(DataInputStream dis = new DataInputStream(inputStream)) {
           byte[] buffer = new byte[4];
           int bytesRead = dis.read(buffer);  // Bytes are read into buffer
           if (bytesRead != 4) {
               throw new IOException("Unexpected End of Stream");
           }
           int serialNumber =
                   ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
       }
   }

上面的例子中,我們使用了ByteBuffer提供的wrap和order方法來對Byte數(shù)組進(jìn)行轉(zhuǎn)換。

當(dāng)然我們也可以自己手動進(jìn)行轉(zhuǎn)換。

還有一個最簡單的方法,就是調(diào)用JDK1.5之后的reverseBytes() 直接進(jìn)行小端到大端的轉(zhuǎn)換。

    public  int reverse(int i) {
       return Integer.reverseBytes(i);
   }

到此,相信大家對“java安全編碼指南之文件IO操作的方法是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI