Otrzymuję ciąg z zewnętrznego procesu. Chcę użyć tego ciągu, aby utworzyć nazwę pliku, a następnie zapisać do tego pliku. Oto mój fragment kodu, aby to zrobić:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), s);
PrintWriter currentWriter = new PrintWriter(currentFile);
Jeśli s zawiera nieprawidłowy znak, taki jak „/” w systemie operacyjnym opartym na systemie Unix, to (słusznie) wyrzucany jest wyjątek java.io.FileNotFoundException.
Jak bezpiecznie zakodować ciąg znaków, aby można go było użyć jako nazwy pliku?
Edycja: liczę na wywołanie interfejsu API, które robi to za mnie.
Mogę to zrobić:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), URLEncoder.encode(s, "UTF-8"));
PrintWriter currentWriter = new PrintWriter(currentFile);
Ale nie jestem pewien, czy URLEncoder jest niezawodny do tego celu.
Odpowiedzi:
Jeśli chcesz, aby wynik przypominał oryginalny plik, SHA-1 lub inny schemat mieszania nie jest odpowiedzią. Jeśli trzeba unikać kolizji, to zwykła zamiana lub usunięcie „złych” znaków również nie jest rozwiązaniem.
Zamiast tego chcesz czegoś takiego. (Uwaga: należy to traktować jako przykład ilustrujący, a nie coś do skopiowania i wklejenia).
To rozwiązanie zapewnia odwracalne kodowanie (bez kolizji), w którym zakodowane ciągi w większości przypadków przypominają oryginalne. Zakładam, że używasz 8-bitowych znaków.
URLEncoder
działa, ale ma tę wadę, że koduje całe mnóstwo dozwolonych znaków nazw plików.Jeśli chcesz mieć rozwiązanie, którego nie gwarantuje się odwracalności, po prostu usuń „złe” znaki zamiast zastępować je sekwencjami ucieczki.
Odwrócenie powyższego kodowania powinno być równie łatwe do wdrożenia.
źródło
Moją sugestią jest przyjęcie podejścia „białej listy”, co oznacza, że nie próbuj odfiltrowywać złych znaków. Zamiast tego określ, co jest w porządku. Możesz odrzucić nazwę pliku lub ją przefiltrować. Jeśli chcesz to przefiltrować:
To powoduje, że każdy znak, który nie jest cyfrą, literą ani podkreśleniem, zostaje zastąpiony niczym. Alternatywnie możesz zastąpić je innym znakiem (np. Podkreśleniem).
Problem polega na tym, że jeśli jest to katalog współdzielony, nie chcesz kolizji nazw plików. Nawet jeśli obszary pamięci użytkowników są segregowane według użytkownika, możesz skończyć z kolidującą nazwą pliku, po prostu odfiltrowując złe znaki. Nazwa wprowadzona przez użytkownika jest często przydatna, jeśli chcą ją również pobrać.
Z tego powodu staram się pozwalać użytkownikowi wprowadzić to, czego chce, przechowywać nazwę pliku w oparciu o wybrany przez siebie schemat (np. UserId_fileId), a następnie przechowywać nazwę pliku użytkownika w tabeli bazy danych. W ten sposób możesz wyświetlić go z powrotem użytkownikowi, przechowywać rzeczy tak, jak chcesz i nie narażasz bezpieczeństwa ani nie usuwasz innych plików.
Możesz również zaszyfrować plik (np. Hash MD5), ale wtedy nie możesz wyświetlić listy plików, które umieścił użytkownik (i tak nie ze zrozumiałą nazwą).
EDYCJA: Naprawiono regex dla java
źródło
"\\W+"
wyrażenia regularnego w Javie. Ukośnik odwrotny najpierw odnosi się do samego ciągu i\W
nie jest prawidłową sekwencją ucieczki. Próbowałem edytować odpowiedź, ale wygląda na to, że ktoś odrzucił moją zmianę :(Zależy to od tego, czy kodowanie powinno być odwracalne, czy nie.
Odwracalny
Użyj kodowania adresu URL (
java.net.URLEncoder
), aby zastąpić znaki specjalne%xx
. Zwróć uwagę, że zajmujesz się specjalnymi przypadkami, w których ciąg jest równy.
, równy..
lub pusty! ¹ Wiele programów używa kodowania adresów URL do tworzenia nazw plików, więc jest to standardowa technika, którą każdy rozumie.Nieodwracalny
Użyj skrótu (np. SHA-1) podanego ciągu. Nowoczesne algorytmy haszujące ( nie MD5) można uznać za bezkolizyjne. W rzeczywistości będziesz miał przełom w kryptografii, jeśli znajdziesz kolizję.
¹ Możesz elegancko obsłużyć wszystkie 3 przypadki specjalne, używając przedrostka, takiego jak
"myApp-"
. Jeśli umieścisz plik bezpośrednio w$HOME
, będziesz musiał to zrobić, aby uniknąć konfliktów z istniejącymi plikami, takimi jak „.bashrc”.źródło
Oto czego używam:
To, co robi, to zastąpienie każdego znaku, który nie jest literą, cyfrą, podkreśleniem lub kropką, podkreśleniem, używając wyrażenia regularnego.
Oznacza to, że coś w rodzaju „Jak zamienić GBP na $” zmieni się w „How_to_convert___to__”. Wprawdzie wynik ten nie jest zbyt przyjazny dla użytkownika, ale jest bezpieczny, a wynikowe nazwy katalogów / plików gwarantują, że będą działać wszędzie. W moim przypadku wynik nie jest wyświetlany użytkownikowi, a zatem nie stanowi problemu, ale możesz chcieć zmienić wyrażenie regularne, aby było bardziej liberalne.
Warto zauważyć, że innym problemem, który napotkałem, było to, że czasami otrzymywałem identyczne nazwy (ponieważ są one oparte na danych wejściowych użytkownika), więc powinieneś być tego świadomy, ponieważ nie możesz mieć wielu katalogów / plików o tej samej nazwie w jednym katalogu . Po prostu dodałem aktualny czas i datę oraz krótki, losowy ciąg, aby tego uniknąć. (rzeczywisty losowy ciąg znaków, a nie skrót nazwy pliku, ponieważ identyczne nazwy plików spowodują identyczne skróty)
Konieczne może być również obcięcie lub w inny sposób skrócenie otrzymanego ciągu, ponieważ może on przekroczyć limit 255 znaków, jaki mają niektóre systemy.
źródło
Dla tych, którzy szukają ogólnego rozwiązania, mogą to być typowe kryteria:
Aby to osiągnąć, możemy użyć wyrażenia regularnego, aby dopasować niedozwolone znaki, zakodować je procentowo , a następnie ograniczyć długość zakodowanego ciągu.
Wzory
Powyższy wzorzec jest oparty na konserwatywnym podzbiorze dozwolonych znaków w specyfikacji POSIX .
Jeśli chcesz zezwolić na znak kropki, użyj:
Uważaj tylko na ciągi typu „”. i ".."
Jeśli chcesz uniknąć kolizji w systemach plików bez rozróżniania wielkości liter, musisz uciec z wielkich liter:
Lub unikaj małych liter:
Zamiast używać białej listy, możesz zdecydować się na czarną listę znaków zastrzeżonych dla swojego konkretnego systemu plików. EG To wyrażenie regularne pasuje do systemów plików FAT32:
Długość
W systemie Android bezpieczny limit to 127 znaków . Wiele systemów plików dopuszcza 255 znaków.
Jeśli wolisz zachować ogon zamiast główki sznurka, użyj:
Rozszyfrowanie
Aby przekonwertować nazwę pliku z powrotem na oryginalny ciąg, użyj:
Ograniczenia
Ponieważ dłuższe łańcuchy są obcinane, istnieje możliwość kolizji nazw podczas kodowania lub uszkodzenia podczas dekodowania.
źródło
Pattern.compile("[^A-Za-z0-9_\\-]")
Spróbuj użyć następującego wyrażenia regularnego, które zastępuje każdy nieprawidłowy znak w nazwie pliku spacją:
źródło
_
lub-
.Wybierz swoją truciznę z opcji przedstawionych przez commons-codec , przykład:
źródło
sha1
;sha
jest przestarzałe.To prawdopodobnie nie jest najbardziej efektywny sposób, ale pokazuje, jak to zrobić za pomocą potoków Java 8:
Rozwiązanie można ulepszyć, tworząc niestandardowy kolektor, który używa StringBuilder, dzięki czemu nie trzeba rzutować każdego lekkiego znaku na ciężki ciąg.
źródło
Możesz usunąć nieprawidłowe znaki („/”, „\”, „?”, „*”), A następnie ich użyć.
źródło