1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.util;
33
34import com.google.common.base.Strings;
35import com.google.common.io.Files;
36import junit.framework.Assert;
37import org.junit.Test;
38
39import java.io.File;
40import java.nio.charset.Charset;
41
42public class ClassFileNameHandlerTest {
43    private final Charset UTF8 = Charset.forName("UTF-8");
44
45    @Test
46    public void test1ByteEncodings() {
47        StringBuilder sb = new StringBuilder();
48        for (int i=0; i<100; i++) {
49            sb.append((char)i);
50        }
51
52        String result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 5);
53        Assert.assertEquals(95, result.getBytes(UTF8).length);
54        Assert.assertEquals(95, result.length());
55    }
56
57    @Test
58    public void test2ByteEncodings() {
59        StringBuilder sb = new StringBuilder();
60        for (int i=0x80; i<0x80+100; i++) {
61            sb.append((char)i);
62        }
63
64        // remove a total of 3 2-byte characters, and then add back in the 1-byte '#'
65        String result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 4);
66        Assert.assertEquals(200, sb.toString().getBytes(UTF8).length);
67        Assert.assertEquals(195, result.getBytes(UTF8).length);
68        Assert.assertEquals(98, result.length());
69
70        // remove a total of 3 2-byte characters, and then add back in the 1-byte '#'
71        result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 5);
72        Assert.assertEquals(200, sb.toString().getBytes(UTF8).length);
73        Assert.assertEquals(195, result.getBytes(UTF8).length);
74        Assert.assertEquals(98, result.length());
75    }
76
77    @Test
78    public void test3ByteEncodings() {
79        StringBuilder sb = new StringBuilder();
80        for (int i=0x800; i<0x800+100; i++) {
81            sb.append((char)i);
82        }
83
84        // remove a total of 3 3-byte characters, and then add back in the 1-byte '#'
85        String result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 6);
86        Assert.assertEquals(300, sb.toString().getBytes(UTF8).length);
87        Assert.assertEquals(292, result.getBytes(UTF8).length);
88        Assert.assertEquals(98, result.length());
89
90        // remove a total of 3 3-byte characters, and then add back in the 1-byte '#'
91        result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 7);
92        Assert.assertEquals(300, sb.toString().getBytes(UTF8).length);
93        Assert.assertEquals(292, result.getBytes(UTF8).length);
94        Assert.assertEquals(98, result.length());
95    }
96
97    @Test
98    public void test4ByteEncodings() {
99        StringBuilder sb = new StringBuilder();
100        for (int i=0x10000; i<0x10000+100; i++) {
101            sb.appendCodePoint(i);
102        }
103
104        // we remove 3 codepoints == 6 characters == 12 bytes, and then add back in the 1-byte '#'
105        String result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 8);
106        Assert.assertEquals(400, sb.toString().getBytes(UTF8).length);
107        Assert.assertEquals(389, result.getBytes(UTF8).length);
108        Assert.assertEquals(195, result.length());
109
110        // we remove 2 codepoints == 4 characters == 8 bytes, and then add back in the 1-byte '#'
111        result = ClassFileNameHandler.shortenPathComponent(sb.toString(), 7);
112        Assert.assertEquals(400, sb.toString().getBytes(UTF8).length);
113        Assert.assertEquals(393, result.getBytes(UTF8).length);
114        Assert.assertEquals(197, result.length());
115    }
116
117    @Test
118    public void testMultipleLongNames() {
119        String filenameFragment = Strings.repeat("a", 512);
120
121        File tempDir = Files.createTempDir();
122        ClassFileNameHandler handler = new ClassFileNameHandler(tempDir, ".smali");
123
124        // put the differentiating character in the middle, where it will get stripped out by the filename shortening
125        // logic
126        File file1 = handler.getUniqueFilenameForClass("La/a/" + filenameFragment  + "1" + filenameFragment + ";");
127        checkFilename(tempDir, file1, "a", "a", Strings.repeat("a", 124) + "#" + Strings.repeat("a", 118) + ".smali");
128
129        File file2 = handler.getUniqueFilenameForClass("La/a/" + filenameFragment + "2" + filenameFragment + ";");
130        checkFilename(tempDir, file2, "a", "a", Strings.repeat("a", 124) + "#" + Strings.repeat("a", 118) + ".1.smali");
131
132        Assert.assertFalse(file1.getAbsolutePath().equals(file2.getAbsolutePath()));
133    }
134
135    @Test
136    public void testBasicFunctionality() {
137        File tempDir = Files.createTempDir();
138        ClassFileNameHandler handler = new ClassFileNameHandler(tempDir, ".smali");
139
140        File file = handler.getUniqueFilenameForClass("La/b/c/d;");
141        checkFilename(tempDir, file, "a", "b", "c", "d.smali");
142
143        file = handler.getUniqueFilenameForClass("La/b/c/e;");
144        checkFilename(tempDir, file, "a", "b", "c", "e.smali");
145
146        file = handler.getUniqueFilenameForClass("La/b/d/d;");
147        checkFilename(tempDir, file, "a", "b", "d", "d.smali");
148
149        file = handler.getUniqueFilenameForClass("La/b;");
150        checkFilename(tempDir, file, "a", "b.smali");
151
152        file = handler.getUniqueFilenameForClass("Lb;");
153        checkFilename(tempDir, file, "b.smali");
154    }
155
156    @Test
157    public void testCaseInsensitiveFilesystem() {
158        File tempDir = Files.createTempDir();
159        ClassFileNameHandler handler = new ClassFileNameHandler(tempDir, ".smali", false, false);
160
161        File file = handler.getUniqueFilenameForClass("La/b/c;");
162        checkFilename(tempDir, file, "a", "b", "c.smali");
163
164        file = handler.getUniqueFilenameForClass("La/b/C;");
165        checkFilename(tempDir, file, "a", "b", "C.1.smali");
166
167        file = handler.getUniqueFilenameForClass("La/B/c;");
168        checkFilename(tempDir, file, "a", "B.1", "c.smali");
169    }
170
171    @Test
172    public void testCaseSensitiveFilesystem() {
173        File tempDir = Files.createTempDir();
174        ClassFileNameHandler handler = new ClassFileNameHandler(tempDir, ".smali", true, false);
175
176        File file = handler.getUniqueFilenameForClass("La/b/c;");
177        checkFilename(tempDir, file, "a", "b", "c.smali");
178
179        file = handler.getUniqueFilenameForClass("La/b/C;");
180        checkFilename(tempDir, file, "a", "b", "C.smali");
181
182        file = handler.getUniqueFilenameForClass("La/B/c;");
183        checkFilename(tempDir, file, "a", "B", "c.smali");
184    }
185
186    @Test
187    public void testWindowsReservedFilenames() {
188        File tempDir = Files.createTempDir();
189        ClassFileNameHandler handler = new ClassFileNameHandler(tempDir, ".smali", false, true);
190
191        File file = handler.getUniqueFilenameForClass("La/con/c;");
192        checkFilename(tempDir, file, "a", "con#", "c.smali");
193
194        file = handler.getUniqueFilenameForClass("La/Con/c;");
195        checkFilename(tempDir, file, "a", "Con#.1", "c.smali");
196
197        file = handler.getUniqueFilenameForClass("La/b/PRN;");
198        checkFilename(tempDir, file, "a", "b", "PRN#.smali");
199
200        file = handler.getUniqueFilenameForClass("La/b/prN;");
201        checkFilename(tempDir, file, "a", "b", "prN#.1.smali");
202
203        file = handler.getUniqueFilenameForClass("La/b/com0;");
204        checkFilename(tempDir, file, "a", "b", "com0.smali");
205
206        for (String reservedName: new String[] {"con", "prn", "aux", "nul", "com1", "com9", "lpt1", "lpt9"}) {
207            file = handler.getUniqueFilenameForClass("L" + reservedName + ";");
208            checkFilename(tempDir, file, reservedName +"#.smali");
209        }
210    }
211
212    @Test
213    public void testIgnoringWindowsReservedFilenames() {
214        File tempDir = Files.createTempDir();
215        ClassFileNameHandler handler = new ClassFileNameHandler(tempDir, ".smali", true, false);
216
217        File file = handler.getUniqueFilenameForClass("La/con/c;");
218        checkFilename(tempDir, file, "a", "con", "c.smali");
219
220        file = handler.getUniqueFilenameForClass("La/Con/c;");
221        checkFilename(tempDir, file, "a", "Con", "c.smali");
222
223        file = handler.getUniqueFilenameForClass("La/b/PRN;");
224        checkFilename(tempDir, file, "a", "b", "PRN.smali");
225
226        file = handler.getUniqueFilenameForClass("La/b/prN;");
227        checkFilename(tempDir, file, "a", "b", "prN.smali");
228
229        file = handler.getUniqueFilenameForClass("La/b/com0;");
230        checkFilename(tempDir, file, "a", "b", "com0.smali");
231
232        for (String reservedName: new String[] {"con", "prn", "aux", "nul", "com1", "com9", "lpt1", "lpt9"}) {
233            file = handler.getUniqueFilenameForClass("L" + reservedName + ";");
234            checkFilename(tempDir, file, reservedName +".smali");
235        }
236    }
237
238    private void checkFilename(File base, File file, String... elements) {
239        for (int i=elements.length-1; i>=0; i--) {
240            Assert.assertEquals(elements[i], file.getName());
241            file = file.getParentFile();
242        }
243        Assert.assertEquals(base.getAbsolutePath(), file.getAbsolutePath());
244    }
245}
246