<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近用到了 org.apache.commons.io.FilenameUtils#getName
這個方法,該方法可以傳入檔案路徑,獲取檔名。 簡單看了下原始碼,雖然並不複雜,但和自己設想略有區別,值得學習,本文簡單分析下。
org.apache.commons.io.FilenameUtils#getName
/** * Gets the name minus the path from a full fileName. * <p> * This method will handle a file in either Unix or Windows format. * The text after the last forward or backslash is returned. * <pre> * a/b/c.txt --> c.txt * a.txt --> a.txt * a/b/c --> c * a/b/c/ --> "" * </pre> * <p> * The output will be the same irrespective of the machine that the code is running on. * * @param fileName the fileName to query, null returns null * @return the name of the file without the path, or an empty string if none exists. * Null bytes inside string will be removed */ public static String getName(final String fileName) { // 傳入 null 直接返回 null if (fileName == null) { return null; } // NonNul 檢查 requireNonNullChars(fileName); // 查詢最後一個分隔符 final int index = indexOfLastSeparator(fileName); // 從最後一個分隔符竊到最後 return fileName.substring(index + 1); }
org.apache.commons.io.FilenameUtils#requireNonNullChars
/** * Checks the input for null bytes, a sign of unsanitized data being passed to to file level functions. * * This may be used for poison byte attacks. * * @param path the path to check */ private static void requireNonNullChars(final String path) { if (path.indexOf(0) >= 0) { throw new IllegalArgumentException("Null byte present in file/path name. There are no " + "known legitimate use cases for such data, but several injection attacks may use it"); } }
java.lang.String#indexOf(int)
原始碼:
/** * Returns the index within this string of the first occurrence of * the specified character. If a character with value * {@code ch} occurs in the character sequence represented by * this {@code String} object, then the index (in Unicode * code units) of the first such occurrence is returned. For * values of {@code ch} in the range from 0 to 0xFFFF * (inclusive), this is the smallest value <i>k</i> such that: * <blockquote><pre> * this.charAt(<i>k</i>) == ch * </pre></blockquote> * is true. For other values of {@code ch}, it is the * smallest value <i>k</i> such that: * <blockquote><pre> * this.codePointAt(<i>k</i>) == ch * </pre></blockquote> * is true. In either case, if no such character occurs in this * string, then {@code -1} is returned. * * @param ch a character (Unicode code point). * @return the index of the first occurrence of the character in the * character sequence represented by this object, or * {@code -1} if the character does not occur. */ public int indexOf(int ch) { return indexOf(ch, 0); }
可知,indexOf(0)
目的是查詢 ASCII 碼為 0 的字元的位置,如果找到則丟擲 IllegalArgumentException
異常。 搜尋 ASCII 對照表,得知 ASCII 值為 0 代表控制字元 NUT,並不是常規的檔名所應該包含的字元。
null 位元組是一個值為 0 的位元組,如十六進位制中的 0x00。 存在與 null 位元組有關的安全漏洞。 因為 C 語言中使用 null 位元組作為字串終結符,而其他語言(Java,PHP等)沒有這個字串終結符; 例如,Java Web 專案只允許使用者上傳 .jpg 格式的圖片,但利用這個漏洞就可以上傳 .jsp 檔案。 如使用者上傳 hack.jsp<NUL>.jpg
檔案, Java 會認為符合 .jpg 格式,實際呼叫 C 語言系統函數寫入磁碟時講 當做字串分隔符,結果將檔案儲存為 hack.jsp
。 有些程式語言不允許在檔名中使用 ·· <NUL>
,如果你使用的程式語言沒有對此處理,就需要自己去處理。 因此,這個檢查很有必要。
程式碼範例:
package org.example; import org.apache.commons.io.FilenameUtils; public class FilenameDemo { public static void main(String[] args) { String filename= "hack.jsp .jpg"; System.out.println( FilenameUtils.getName(filename)); } }
報錯資訊:
Exception in thread "main" java.lang.IllegalArgumentException: Null byte present in file/path name. There are no known legitimate use cases for such data, but several injection attacks may use it
at org.apache.commons.io.FilenameUtils.requireNonNullChars(FilenameUtils.java:998)
at org.apache.commons.io.FilenameUtils.getName(FilenameUtils.java:984)
at org.example.FilenameDemo.main(FilenameDemo.java:8)
如果去掉校驗:
package org.example; import org.apache.commons.io.FilenameUtils; public class FilenameDemo { public static void main(String[] args) { String filename= "hack.jsp .jpg"; // 不新增校驗 String name = getName(filename); // 獲取拓展名 String extension = FilenameUtils.getExtension(name); System.out.println(extension); } public static String getName(final String fileName) { if (fileName == null) { return null; } final int index = FilenameUtils.indexOfLastSeparator(fileName); return fileName.substring(index + 1); } }
Java 的確會將拓展名識別為 jpg
jpg
JDK 8 及其以上版本試圖建立 hack.jsp .jpg
的檔案時,底層也會做類似的校驗,無法建立成功。
大家感興趣可以試試使用 C 語言寫入名為 hack.jsp .jpg
的檔案,最終很可能檔名為 hack.jsp
。
查詢最後一個分隔符 org.apache.commons.io.FilenameUtils#indexOfLastSeparator
/** * Returns the index of the last directory separator character. * <p> * This method will handle a file in either Unix or Windows format. * The position of the last forward or backslash is returned. * <p> * The output will be the same irrespective of the machine that the code is running on. * * @param fileName the fileName to find the last path separator in, null returns -1 * @return the index of the last separator character, or -1 if there * is no such character */ public static int indexOfLastSeparator(final String fileName) { if (fileName == null) { return NOT_FOUND; } final int lastUnixPos = fileName.lastIndexOf(UNIX_SEPARATOR); final int lastWindowsPos = fileName.lastIndexOf(WINDOWS_SEPARATOR); return Math.max(lastUnixPos, lastWindowsPos); }
該方法的語意是獲取檔名,那麼從函數的語意層面上來說,不管是啥系統的檔案分隔符都必須要保證得到正確的檔名。 試想一下,在 Windows 系統上呼叫該函數,傳入一個 Unix 檔案路徑,得不到正確的檔名合理嗎? 函數設計本身就應該考慮相容性。 因此不能獲取當前系統的分隔符來擷取檔名。 原始碼中分別獲取 Window 和 Unix 分隔符,有哪個用哪個,顯然更加合理。
我們日常編碼時,要做防禦性程式設計,對於錯誤的、非法的輸入都要做好預防。
我們寫程式碼一定不要想當然。 我們先想清楚這個函數究竟要實現怎樣的功能,而且不是做一個 “CV 工程師”,無腦“拷貝”程式碼。 同時,我們也應該寫好單測,充分考慮各種異常 Case ,保證正常和異常的 Case 都覆蓋到。
org.apache.commons.io.FilenameUtils#requireNonNullChars
函數註釋部分就給出了這麼設計的原因:This may be used for poison byte attacks.
註釋不應該“喃喃自語”講一些顯而易見的廢話。 對於容易讓人困惑的設計,一定要通過註釋講清楚設計原因。
此外,結合工作經驗,推薦一些其他註釋技巧: (1)對於稍微複雜或者重要的設計,可以通過註釋給出核心的設計思路; 如: java.util.concurrent.ThreadPoolExecutor#execute
/** * Executes the given task sometime in the future. The task * may execute in a new thread or in an existing pooled thread. * * If the task cannot be submitted for execution, either because this * executor has been shutdown or because its capacity has been reached, * the task is handled by the current {@link RejectedExecutionHandler}. * * @param command the task to execute * @throws RejectedExecutionException at discretion of * {@code RejectedExecutionHandler}, if the task * cannot be accepted for execution * @throws NullPointerException if {@code command} is null */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
(2)對於關聯的程式碼,可以使用 @see 或者 {@link } 的方式,在程式碼中提供關聯程式碼的快捷跳轉方式。
/** * Sets the core number of threads. This overrides any value set * in the constructor. If the new value is smaller than the * current value, excess existing threads will be terminated when * they next become idle. If larger, new threads will, if needed, * be started to execute any queued tasks. * * @param corePoolSize the new core size * @throws IllegalArgumentException if {@code corePoolSize < 0} * or {@code corePoolSize} is greater than the {@linkplain * #getMaximumPoolSize() maximum pool size} * @see #getCorePoolSize */ public void setCorePoolSize(int corePoolSize) { if (corePoolSize < 0 || maximumPoolSize < corePoolSize) throw new IllegalArgumentException(); int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; if (workerCountOf(ctl.get()) > corePoolSize) interruptIdleWorkers(); else if (delta > 0) { // We don't really know how many new threads are "needed". // As a heuristic, prestart enough new workers (up to new // core size) to handle the current number of tasks in // queue, but stop if queue becomes empty while doing so. int k = Math.min(delta, workQueue.size()); while (k-- > 0 && addWorker(null, true)) { if (workQueue.isEmpty()) break; } } }
(2)在日常業務開發中,非常推薦講相關的檔案、設定頁面連結也放到註釋中,極大方便後期維護。 如:
/** * 某某功能 * * 相關檔案: * <a href="https://blog.csdn.net/w605283073" rel="external nofollow" rel="external nofollow" >設計檔案</a> * <a href="https://blog.csdn.net/w605283073" rel="external nofollow" rel="external nofollow" >三方API地址</a> */ public void demo(){ // 省略 }
(4)對於工具類可以考慮講給出常見的輸入對應的輸出。 如 org.apache.commons.lang3.StringUtils#center(java.lang.String, int, char)
/** * <p>Centers a String in a larger String of size {@code size}. * Uses a supplied character as the value to pad the String with.</p> * * <p>If the size is less than the String length, the String is returned. * A {@code null} String returns {@code null}. * A negative size is treated as zero.</p> * * <pre> * StringUtils.center(null, *, *) = null * StringUtils.center("", 4, ' ') = " " * StringUtils.center("ab", -1, ' ') = "ab" * StringUtils.center("ab", 4, ' ') = " ab " * StringUtils.center("abcd", 2, ' ') = "abcd" * StringUtils.center("a", 4, ' ') = " a " * StringUtils.center("a", 4, 'y') = "yayy" * </pre> * * @param str the String to center, may be null * @param size the int size of new String, negative treated as zero * @param padChar the character to pad the new String with * @return centered String, {@code null} if null String input * @since 2.0 */ public static String center(String str, final int size, final char padChar) { if (str == null || size <= 0) { return str; } final int strLen = str.length(); final int pads = size - strLen; if (pads <= 0) { return str; } str = leftPad(str, strLen + pads / 2, padChar); str = rightPad(str, size, padChar); return str; }
(5) 對於廢棄的方法,一定要註明廢棄的原因,給出替代方案。 如:java.security.Signature#setParameter(java.lang.String, java.lang.Object)
/** * 省略部分 * * @see #getParameter * * @deprecated Use * {@link #setParameter(java.security.spec.AlgorithmParameterSpec) * setParameter}. */ @Deprecated public final void setParameter(String param, Object value) throws InvalidParameterException { engineSetParameter(param, value); }
很多優秀的開源專案的程式碼設計都非常嚴謹,往往簡單的程式碼中也蘊藏著縝密的思考。 我們有時間可以看看一些優秀的開源專案,可以從簡單的入手,可以先想想如果自己寫大概該如何實現,然後和作者的實現思路對比,會有更大收穫。 平時看原始碼時,不僅要知道原始碼長這樣,更要了解為什麼這麼設計。
以上就是FilenameUtils.getName 函數原始碼分析的詳細內容,更多關於FilenameUtils.getName 函數的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45