|
1 | 1 | package iso9660 |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/binary" |
4 | 5 | "fmt" |
5 | 6 | "io" |
6 | 7 | "os" |
7 | 8 | "strings" |
8 | 9 | "time" |
| 10 | + "unicode/utf16" |
9 | 11 | ) |
10 | 12 |
|
11 | 13 | // Image is a wrapper around an image file that allows reading its ISO9660 data |
@@ -51,9 +53,18 @@ func (i *Image) readVolumes() error { |
51 | 53 | return nil |
52 | 54 | } |
53 | 55 |
|
54 | | -// RootDir returns the File structure corresponding to the root directory |
55 | | -// of the first primary volume |
| 56 | +// RootDir returns the File structure corresponding to the root directory. |
| 57 | +// It prefers a Joliet supplementary volume descriptor (which provides full |
| 58 | +// Unicode filenames) over the primary volume descriptor. |
56 | 59 | func (i *Image) RootDir() (*File, error) { |
| 60 | + // Check for Joliet supplementary VD first. |
| 61 | + for _, vd := range i.volumeDescriptors { |
| 62 | + if vd.isJoliet() { |
| 63 | + return &File{de: vd.Primary.RootDirectoryEntry, ra: i.ra, children: nil, isRootDir: true, joliet: true}, nil |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + // Fall back to primary VD. |
57 | 68 | for _, vd := range i.volumeDescriptors { |
58 | 69 | if vd.Type() == volumeTypePrimary { |
59 | 70 | return &File{de: vd.Primary.RootDirectoryEntry, ra: i.ra, children: nil, isRootDir: true}, nil |
@@ -85,6 +96,7 @@ type File struct { |
85 | 96 | children []*File |
86 | 97 | isRootDir bool |
87 | 98 | susp *SUSPMetadata |
| 99 | + joliet bool |
88 | 100 | // extents holds all extents for multi-extent files (ECMA-119 9.1.6). |
89 | 101 | // For single-extent files this is nil and de.ExtentLocation/ExtentLength are used directly. |
90 | 102 | extents []extent |
@@ -141,6 +153,15 @@ func (f *File) Name() string { |
141 | 153 | } |
142 | 154 | } |
143 | 155 |
|
| 156 | + // Joliet names are already decoded to UTF-8; just strip any trailing ";1". |
| 157 | + if f.joliet { |
| 158 | + name := f.de.Identifier |
| 159 | + if idx := strings.LastIndex(name, ";"); idx >= 0 { |
| 160 | + name = name[:idx] |
| 161 | + } |
| 162 | + return name |
| 163 | + } |
| 164 | + |
144 | 165 | if f.IsDir() { |
145 | 166 | return f.de.Identifier |
146 | 167 | } |
@@ -224,6 +245,11 @@ func (f *File) GetAllChildren() ([]*File, error) { |
224 | 245 | return nil, err |
225 | 246 | } |
226 | 247 |
|
| 248 | + // Decode Joliet UTF-16BE identifiers to UTF-8. |
| 249 | + if f.joliet && len(newDE.Identifier) > 1 { |
| 250 | + newDE.Identifier = decodeJolietIdentifier([]byte(newDE.Identifier)) |
| 251 | + } |
| 252 | + |
227 | 253 | // Is this a root directory '.' record? |
228 | 254 | if f.isRootDir && newDE.Identifier == string([]byte{0}) { |
229 | 255 | newDE.SystemUseEntries, _ = splitSystemUseEntries(newDE.SystemUse, f.ra) |
@@ -273,6 +299,7 @@ func (f *File) GetAllChildren() ([]*File, error) { |
273 | 299 | de: newDE, |
274 | 300 | children: nil, |
275 | 301 | susp: f.susp.Clone(), |
| 302 | + joliet: f.joliet, |
276 | 303 | } |
277 | 304 |
|
278 | 305 | // If we accumulated multi-extent records, finalize them now. |
@@ -350,3 +377,17 @@ func (f *File) Reader() io.Reader { |
350 | 377 | baseOffset := int64(f.de.ExtentLocation) * int64(sectorSize) |
351 | 378 | return io.NewSectionReader(f.ra, baseOffset, int64(f.de.ExtentLength)) |
352 | 379 | } |
| 380 | + |
| 381 | +// decodeJolietIdentifier decodes a UTF-16BE encoded Joliet identifier to UTF-8. |
| 382 | +func decodeJolietIdentifier(raw []byte) string { |
| 383 | + if len(raw)%2 != 0 { |
| 384 | + return string(raw) |
| 385 | + } |
| 386 | + |
| 387 | + u16 := make([]uint16, len(raw)/2) |
| 388 | + for i := range u16 { |
| 389 | + u16[i] = binary.BigEndian.Uint16(raw[2*i : 2*i+2]) |
| 390 | + } |
| 391 | + |
| 392 | + return string(utf16.Decode(u16)) |
| 393 | +} |
0 commit comments