1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2006-2019. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(zip). 21 22%% Basic api 23-export([unzip/1, unzip/2, extract/1, extract/2, 24 zip/2, zip/3, create/2, create/3, foldl/3, 25 list_dir/1, list_dir/2, table/1, table/2, 26 t/1, tt/1]). 27 28%% unzipping piecemeal 29-export([openzip_open/1, openzip_open/2, 30 openzip_get/1, openzip_get/2, 31 openzip_t/1, openzip_tt/1, 32 openzip_list_dir/1, openzip_list_dir/2, 33 openzip_close/1]). 34%% openzip_add/2]). 35 36%% zip server 37-export([zip_open/1, zip_open/2, 38 zip_get/1, zip_get/2, 39 zip_t/1, zip_tt/1, 40 zip_list_dir/1, zip_list_dir/2, 41 zip_close/1]). 42 43%% just for debugging zip server, not documented, not tested, not to be used 44-export([zip_get_state/1]). 45 46%% includes 47-include("file.hrl"). % #file_info 48-include("zip.hrl"). % #zip_file, #zip_comment 49 50%% max bytes fed to zlib 51-define(WRITE_BLOCK_SIZE, 8*1024). 52 53%% for debugging, to turn off catch 54-define(CATCH, catch). 55 56%% Debug. 57-define(SHOW_GP_BIT_11(B, F), ok). 58%%-define(SHOW_GP_BIT_11(B, F), io:format("F = ~.16#, B = ~lp\n", [F, B])). 59 60%% option sets 61-record(unzip_opts, { 62 output, % output object (fun) 63 input, % input object (fun) 64 file_filter, % file filter (boolean fun) 65 open_opts, % options passed to file:open 66 feedback, % feeback (fun) 67 cwd % directory to relate paths to 68 }). 69 70-record(zip_opts, { 71 output, % output object (fun) 72 input, % input object (fun) 73 comment, % zip-file comment 74 open_opts, % options passed to file:open 75 feedback, % feeback (fun) 76 cwd, % directory to relate paths to 77 compress, % compress files with these suffixes 78 uncompress % uncompress files with these suffixes 79 }). 80 81-record(list_dir_opts, { 82 input, % input object (fun) 83 raw_iterator, % applied to each dir entry 84 open_opts % options passed to file:open 85 }). 86 87-record(openzip_opts, { 88 output, % output object (fun) 89 open_opts, % file:open options 90 cwd % directory to relate paths to 91 }). 92 93% openzip record, state for an open zip-file 94-record(openzip, { 95 zip_comment, % zip archive comment 96 files, % filenames, infos, comments and offsets 97 in, % archive handle 98 input, % archive io object (fun) 99 output, % output io object (fun) 100 zlib, % handle to open zlib 101 cwd % directory to relate paths to 102 }). 103 104% Things that I would like to add to the public record #zip_file, 105% but can't as it would make things fail at upgrade. 106% Instead we use {#zip_file,#zip_file_extra} internally. 107-record(zip_file_extra, { 108 crc32 % checksum 109 }). 110 111%% max bytes read from files and archives (and fed to zlib) 112-define(READ_BLOCK_SIZE, 16*1024). 113 114%% -record(primzip_file, { 115%% name, 116%% offset, 117%% chunk_size 118%% }). 119 120%% -record(primzip, { 121%% zlib, % handle to the zlib port from zlib:open 122%% input, % fun/2 for file/memory input 123%% in, % input (file handle or binary) 124%% files % [#primzip_file] 125%% }). 126 127%% ZIP-file format records and defines 128 129%% compression methods 130-define(STORED, 0). 131-define(UNCOMPRESSED, 0). 132-define(SHRUNK, 1). 133-define(REDUCED_1, 2). 134-define(REDUCED_2, 3). 135-define(REDUCED_3, 4). 136-define(REDUCED_4, 5). 137-define(IMPLODED, 6). 138-define(TOKENIZED, 7). 139-define(DEFLATED, 8). 140-define(DEFLATED_64, 9). 141-define(PKWARE_IMPLODED, 10). 142-define(PKWARE_RESERVED, 11). 143-define(BZIP2_COMPRESSED, 12). 144 145%% Version 2.0, attribute compatibility type 3 (Unix) 146-define(VERSION_MADE_BY, 20 bor (3 bsl 8)). 147-define(GP_BIT_11, 16#800). % Filename and file comment UTF-8 encoded. 148 149%% zip-file records 150-define(LOCAL_FILE_MAGIC,16#04034b50). 151-define(LOCAL_FILE_HEADER_SZ,(4+2+2+2+2+2+4+4+4+2+2)). 152-define(LOCAL_FILE_HEADER_CRC32_OFFSET, 4+2+2+2+2+2). 153-record(local_file_header, {version_needed, 154 gp_flag, 155 comp_method, 156 last_mod_time, 157 last_mod_date, 158 crc32, 159 comp_size, 160 uncomp_size, 161 file_name_length, 162 extra_field_length, 163 type}). 164 165-define(CENTRAL_FILE_HEADER_SZ,(4+2+2+2+2+2+2+4+4+4+2+2+2+2+2+4+4)). 166 167-define(CENTRAL_DIR_MAGIC, 16#06054b50). 168-define(CENTRAL_DIR_SZ, (4+2+2+2+2+4+4+2)). 169-define(CENTRAL_DIR_DIGITAL_SIG_MAGIC, 16#05054b50). 170-define(CENTRAL_DIR_DIGITAL_SIG_SZ, (4+2)). 171-define(CENTRAL_REGULAR_FILE_EXT_ATTRIBUTES, 8#644 bsl 16). 172-define(CENTRAL_DIRECTORY_FILE_EXT_ATTRIBUTES, 8#744 bsl 16). 173-define(CENTRAL_FILE_MAGIC, 16#02014b50). 174 175-record(cd_file_header, {version_made_by, 176 version_needed, 177 gp_flag, 178 comp_method, 179 last_mod_time, 180 last_mod_date, 181 crc32, 182 comp_size, 183 uncomp_size, 184 file_name_length, 185 extra_field_length, 186 file_comment_length, 187 disk_num_start, 188 internal_attr, 189 external_attr, 190 local_header_offset}). 191 192-define(END_OF_CENTRAL_DIR_MAGIC, 16#06054b50). 193-define(END_OF_CENTRAL_DIR_SZ, (4+2+2+2+2+4+4+2)). 194 195-record(eocd, {disk_num, 196 start_disk_num, 197 entries_on_disk, 198 entries, 199 size, 200 offset, 201 zip_comment_length}). 202 203 204-type create_option() :: memory | cooked | verbose 205 | {comment, Comment ::string()} 206 | {cwd, CWD :: file:filename()} 207 | {compress, What :: extension_spec()} 208 | {uncompress, What :: extension_spec()}. 209-type extension() :: string(). 210-type extension_spec() :: all 211 | [Extension :: extension()] 212 | {add, [Extension :: extension()]} 213 | {del, [Extension :: extension()]}. 214-type filename() :: file:filename(). 215 216-type zip_comment() :: #zip_comment{}. 217-type zip_file() :: #zip_file{}. 218 219-opaque handle() :: pid(). 220 221-export_type([create_option/0, filename/0, handle/0]). 222 223%% Open a zip archive with options 224%% 225 226openzip_open(F) -> 227 openzip_open(F, []). 228 229openzip_open(F, Options) -> 230 case ?CATCH do_openzip_open(F, Options) of 231 {ok, OpenZip} -> 232 {ok, OpenZip}; 233 Error -> 234 {error, Error} 235 end. 236 237do_openzip_open(F, Options) -> 238 Opts = get_openzip_options(Options), 239 #openzip_opts{output = Output, open_opts = OpO, cwd = CWD} = Opts, 240 Input = get_input(F), 241 In0 = Input({open, F, OpO -- [write]}, []), 242 {[#zip_comment{comment = C} | Files], In1} = 243 get_central_dir(In0, fun raw_file_info_etc/5, Input), 244 Z = zlib:open(), 245 {ok, #openzip{zip_comment = C, 246 files = Files, 247 in = In1, 248 input = Input, 249 output = Output, 250 zlib = Z, 251 cwd = CWD}}. 252 253%% retrieve all files from an open archive 254openzip_get(OpenZip) -> 255 case ?CATCH do_openzip_get(OpenZip) of 256 {ok, Result} -> {ok, Result}; 257 Error -> {error, Error} 258 end. 259 260do_openzip_get(#openzip{files = Files, in = In0, input = Input, 261 output = Output, zlib = Z, cwd = CWD}) -> 262 ZipOpts = #unzip_opts{output = Output, input = Input, 263 file_filter = fun all/1, open_opts = [], 264 feedback = fun silent/1, cwd = CWD}, 265 R = get_z_files(Files, Z, In0, ZipOpts, []), 266 {ok, R}; 267do_openzip_get(_) -> 268 throw(einval). 269 270%% retrieve a file from an open archive 271openzip_get(FileName, OpenZip) -> 272 case ?CATCH do_openzip_get(FileName, OpenZip) of 273 {ok, Result} -> {ok, Result}; 274 Error -> {error, Error} 275 end. 276 277do_openzip_get(F, #openzip{files = Files, in = In0, input = Input, 278 output = Output, zlib = Z, cwd = CWD}) -> 279 %%case lists:keysearch(F, #zip_file.name, Files) of 280 case file_name_search(F, Files) of 281 {#zip_file{offset = Offset},_}=ZFile -> 282 In1 = Input({seek, bof, Offset}, In0), 283 case get_z_file(In1, Z, Input, Output, [], fun silent/1, 284 CWD, ZFile, fun all/1) of 285 {file, R, _In2} -> {ok, R}; 286 _ -> throw(file_not_found) 287 end; 288 _ -> throw(file_not_found) 289 end; 290do_openzip_get(_, _) -> 291 throw(einval). 292 293file_name_search(Name,Files) -> 294 Fun = fun({ZipFile,_}) -> 295 not string:equal(ZipFile#zip_file.name, Name, 296 _IgnoreCase = false, _Norm = nfc) 297 end, 298 case lists:dropwhile(Fun, Files) of 299 [ZFile|_] -> ZFile; 300 [] -> false 301 end. 302 303%% %% add a file to an open archive 304%% openzip_add(File, OpenZip) -> 305%% case ?CATCH do_openzip_add(File, OpenZip) of 306%% {ok, Result} -> {ok, Result}; 307%% Error -> {error, Error} 308%% end. 309 310%% do_openzip_add(File, #open_zip{files = Files, in = In0, 311%% opts = Opts} = OpenZip0) -> 312%% throw(nyi), 313%% Z = zlib:open(), 314%% R = get_z_files(Files, In0, Z, Opts, []), 315%% zlib:close(Z), 316%% {ok, R}; 317%% do_openzip_add(_, _) -> 318%% throw(einval). 319 320%% get file list from open archive 321openzip_list_dir(#openzip{zip_comment = Comment, 322 files = Files}) -> 323 {ZipFiles,_Extras} = lists:unzip(Files), 324 {ok, [#zip_comment{comment = Comment} | ZipFiles]}; 325openzip_list_dir(_) -> 326 {error, einval}. 327 328openzip_list_dir(#openzip{files = Files}, [names_only]) -> 329 {ZipFiles,_Extras} = lists:unzip(Files), 330 Names = [Name || {#zip_file{name=Name},_} <- ZipFiles], 331 {ok, Names}; 332openzip_list_dir(_, _) -> 333 {error, einval}. 334 335%% close an open archive 336openzip_close(#openzip{in = In0, input = Input, zlib = Z}) -> 337 Input(close, In0), 338 zlib:close(Z); 339openzip_close(_) -> 340 {error, einval}. 341 342%% Extract from a zip archive with options 343%% 344%% Accepted options: 345%% verbose, cooked, file_list, keep_old_files, file_filter, memory 346 347-spec(unzip(Archive) -> RetValue when 348 Archive :: file:name() | binary(), 349 RetValue :: {ok, FileList} 350 | {ok, FileBinList} 351 | {error, Reason :: term()} 352 | {error, {Name :: file:name(), Reason :: term()}}, 353 FileList :: [file:name()], 354 FileBinList :: [{file:name(),binary()}]). 355 356unzip(F) -> unzip(F, []). 357 358-spec(unzip(Archive, Options) -> RetValue when 359 Archive :: file:name() | binary(), 360 Options :: [Option], 361 Option :: {file_list, FileList} | cooked 362 | keep_old_files | verbose | memory | 363 {file_filter, FileFilter} | {cwd, CWD}, 364 FileList :: [file:name()], 365 FileBinList :: [{file:name(),binary()}], 366 FileFilter :: fun((ZipFile) -> boolean()), 367 CWD :: file:filename(), 368 ZipFile :: zip_file(), 369 RetValue :: {ok, FileList} 370 | {ok, FileBinList} 371 | {error, Reason :: term()} 372 | {error, {Name :: file:name(), Reason :: term()}}). 373 374unzip(F, Options) -> 375 case ?CATCH do_unzip(F, Options) of 376 {ok, R} -> {ok, R}; 377 Error -> {error, Error} 378 end. 379 380do_unzip(F, Options) -> 381 Opts = get_unzip_options(F, Options), 382 #unzip_opts{input = Input, open_opts = OpO} = Opts, 383 In0 = Input({open, F, OpO -- [write]}, []), 384 RawIterator = fun raw_file_info_etc/5, 385 {Info, In1} = get_central_dir(In0, RawIterator, Input), 386 %% get rid of zip-comment 387 Z = zlib:open(), 388 Files = try 389 get_z_files(Info, Z, In1, Opts, []) 390 after 391 zlib:close(Z), 392 Input(close, In1) 393 end, 394 {ok, Files}. 395 396%% Iterate over all files in a zip archive 397-spec(foldl(Fun, Acc0, Archive) -> {ok, Acc1} | {error, Reason} when 398 Fun :: fun((FileInArchive, GetInfo, GetBin, AccIn) -> AccOut), 399 FileInArchive :: file:name(), 400 GetInfo :: fun(() -> file:file_info()), 401 GetBin :: fun(() -> binary()), 402 Acc0 :: term(), 403 Acc1 :: term(), 404 AccIn :: term(), 405 AccOut :: term(), 406 Archive :: file:name() | {file:name(), binary()}, 407 Reason :: term()). 408 409foldl(Fun, Acc0, Archive) when is_function(Fun, 4) -> 410 ZipFun = 411 fun({Name, GetInfo, GetBin}, A) -> 412 A2 = Fun(Name, GetInfo, GetBin, A), 413 {true, false, A2} 414 end, 415 case prim_zip:open(ZipFun, Acc0, Archive) of 416 {ok, PrimZip, Acc1} -> 417 ok = prim_zip:close(PrimZip), 418 {ok, Acc1}; 419 {error, bad_eocd} -> 420 {error, "Not an archive file"}; 421 {error, Reason} -> 422 {error, Reason} 423 end; 424foldl(_,_, _) -> 425 {error, einval}. 426 427%% Create zip archive name F from Files or binaries 428%% 429%% Accepted options: 430%% verbose, cooked, memory, comment 431 432-spec(zip(Name, FileList) -> RetValue when 433 Name :: file:name(), 434 FileList :: [FileSpec], 435 FileSpec :: file:name() | {file:name(), binary()} 436 | {file:name(), binary(), file:file_info()}, 437 RetValue :: {ok, FileName :: file:name()} 438 | {ok, {FileName :: file:name(), binary()}} 439 | {error, Reason :: term()}). 440 441zip(F, Files) -> zip(F, Files, []). 442 443-spec(zip(Name, FileList, Options) -> RetValue when 444 Name :: file:name(), 445 FileList :: [FileSpec], 446 FileSpec :: file:name() | {file:name(), binary()} 447 | {file:name(), binary(), file:file_info()}, 448 Options :: [Option], 449 Option :: create_option(), 450 RetValue :: {ok, FileName :: file:name()} 451 | {ok, {FileName :: file:name(), binary()}} 452 | {error, Reason :: term()}). 453 454zip(F, Files, Options) -> 455 case ?CATCH do_zip(F, Files, Options) of 456 {ok, R} -> {ok, R}; 457 Error -> {error, Error} 458 end. 459 460do_zip(F, Files, Options) -> 461 Opts = get_zip_options(Files, Options), 462 #zip_opts{output = Output, open_opts = OpO} = Opts, 463 Out0 = Output({open, F, OpO}, []), 464 Z = zlib:open(), 465 try 466 {Out1, LHS, Pos} = put_z_files(Files, Z, Out0, 0, Opts, []), 467 zlib:close(Z), 468 Out2 = put_central_dir(LHS, Pos, Out1, Opts), 469 Out3 = Output({close, F}, Out2), 470 {ok, Out3} 471 catch 472 C:R:Stk -> 473 zlib:close(Z), 474 Output({close, F}, Out0), 475 erlang:raise(C, R, Stk) 476 end. 477 478 479%% List zip directory contents 480%% 481%% Accepted options: 482%% cooked, file_filter, file_output (latter 2 undocumented) 483 484-spec(list_dir(Archive) -> RetValue when 485 Archive :: file:name() | binary(), 486 RetValue :: {ok, CommentAndFiles} | {error, Reason :: term()}, 487 CommentAndFiles :: [zip_comment() | zip_file()]). 488 489list_dir(F) -> list_dir(F, []). 490 491-spec(list_dir(Archive, Options) -> RetValue when 492 Archive :: file:name() | binary(), 493 RetValue :: {ok, CommentAndFiles} | {error, Reason :: term()}, 494 CommentAndFiles :: [zip_comment() | zip_file()], 495 Options :: [Option], 496 Option :: cooked). 497 498list_dir(F, Options) -> 499 case ?CATCH do_list_dir(F, Options) of 500 {ok, R} -> {ok, R}; 501 Error -> {error, Error} 502 end. 503 504do_list_dir(F, Options) -> 505 Opts = get_list_dir_options(F, Options), 506 #list_dir_opts{input = Input, open_opts = OpO, 507 raw_iterator = RawIterator} = Opts, 508 In0 = Input({open, F, OpO}, []), 509 {Info, In1} = get_central_dir(In0, RawIterator, Input), 510 Input(close, In1), 511 {ok, Info}. 512 513%% Print zip directory in short form 514 515-spec(t(Archive) -> ok when 516 Archive :: file:name() | binary() | ZipHandle, 517 ZipHandle :: handle()). 518 519t(F) when is_pid(F) -> zip_t(F); 520t(F) when is_record(F, openzip) -> openzip_t(F); 521t(F) -> t(F, fun raw_short_print_info_etc/5). 522 523t(F, RawPrint) -> 524 case ?CATCH do_t(F, RawPrint) of 525 ok -> ok; 526 Error -> {error, Error} 527 end. 528 529do_t(F, RawPrint) -> 530 Input = get_input(F), 531 OpO = [raw], 532 In0 = Input({open, F, OpO}, []), 533 {_Info, In1} = get_central_dir(In0, RawPrint, Input), 534 Input(close, In1), 535 ok. 536 537%% Print zip directory in long form (like ls -l) 538 539-spec(tt(Archive) -> ok when 540 Archive :: file:name() | binary() | ZipHandle, 541 ZipHandle :: handle()). 542 543tt(F) when is_pid(F) -> zip_tt(F); 544tt(F) when is_record(F, openzip) -> openzip_tt(F); 545tt(F) -> t(F, fun raw_long_print_info_etc/5). 546 547 548%% option utils 549get_unzip_opt([], Opts) -> 550 Opts; 551get_unzip_opt([verbose | Rest], Opts) -> 552 get_unzip_opt(Rest, Opts#unzip_opts{feedback = fun verbose_unzip/1}); 553get_unzip_opt([cooked | Rest], #unzip_opts{open_opts = OpO} = Opts) -> 554 get_unzip_opt(Rest, Opts#unzip_opts{open_opts = OpO -- [raw]}); 555get_unzip_opt([memory | Rest], Opts) -> 556 get_unzip_opt(Rest, Opts#unzip_opts{output = fun binary_io/2}); 557get_unzip_opt([{cwd, CWD} | Rest], Opts) -> 558 get_unzip_opt(Rest, Opts#unzip_opts{cwd = CWD}); 559get_unzip_opt([{file_filter, F} | Rest], Opts) -> 560 Filter1 = fun({ZipFile,_Extra}) -> F(ZipFile) end, 561 Filter2 = fun_and_1(Filter1, Opts#unzip_opts.file_filter), 562 get_unzip_opt(Rest, Opts#unzip_opts{file_filter = Filter2}); 563get_unzip_opt([{file_list, L} | Rest], Opts) -> 564 FileInList = fun(F) -> file_in_list(F, L) end, 565 Filter = fun_and_1(FileInList, Opts#unzip_opts.file_filter), 566 get_unzip_opt(Rest, Opts#unzip_opts{file_filter = Filter}); 567get_unzip_opt([keep_old_files | Rest], Opts) -> 568 Keep = fun keep_old_file/1, 569 Filter = fun_and_1(Keep, Opts#unzip_opts.file_filter), 570 get_unzip_opt(Rest, Opts#unzip_opts{file_filter = Filter}); 571get_unzip_opt([Unknown | _Rest], _Opts) -> 572 throw({bad_option, Unknown}). 573 574get_list_dir_opt([], Opts) -> 575 Opts; 576get_list_dir_opt([cooked | Rest], #list_dir_opts{open_opts = OpO} = Opts) -> 577 get_list_dir_opt(Rest, Opts#list_dir_opts{open_opts = OpO -- [raw]}); 578get_list_dir_opt([names_only | Rest], Opts) -> 579 get_list_dir_opt(Rest, Opts#list_dir_opts{ 580 raw_iterator = fun(A, B, C, D, E) -> raw_name_only(A, B, C, D, E) end}); 581%% get_list_dir_opt([{file_output, F} | Rest], Opts) -> 582%% get_list_dir_opt(Rest, Opts#list_dir_opts{file_output = F}); 583%% get_list_dir_opt([{file_filter, F} | Rest], Opts) -> 584%% get_list_dir_opt(Rest, Opts#list_dir_opts{file_filter = F}); 585get_list_dir_opt([Unknown | _Rest], _Opts) -> 586 throw({bad_option, Unknown}). 587 588get_zip_opt([], Opts) -> 589 Opts; 590get_zip_opt([verbose | Rest], Opts) -> 591 get_zip_opt(Rest, Opts#zip_opts{feedback = fun verbose_zip/1}); 592get_zip_opt([cooked | Rest], #zip_opts{open_opts = OpO} = Opts) -> 593 get_zip_opt(Rest, Opts#zip_opts{open_opts = OpO -- [raw]}); 594get_zip_opt([memory | Rest], Opts) -> 595 get_zip_opt(Rest, Opts#zip_opts{output = fun binary_io/2}); 596get_zip_opt([{cwd, CWD} | Rest], Opts) -> 597 get_zip_opt(Rest, Opts#zip_opts{cwd = CWD}); 598get_zip_opt([{comment, C} | Rest], Opts) -> 599 get_zip_opt(Rest, Opts#zip_opts{comment = C}); 600get_zip_opt([{compress, Which} = O| Rest], Opts) -> 601 Which2 = 602 case Which of 603 all -> 604 all; 605 Suffixes when is_list(Suffixes) -> 606 lists:usort(Suffixes); 607 {add, Suffixes} when is_list(Suffixes) -> 608 lists:usort(Opts#zip_opts.compress ++ Suffixes); 609 {del, Suffixes} when is_list(Suffixes) -> 610 lists:usort(Opts#zip_opts.compress -- Suffixes); 611 _ -> 612 throw({bad_option, O}) 613 end, 614 get_zip_opt(Rest, Opts#zip_opts{compress = Which2}); 615get_zip_opt([{uncompress, Which} = O| Rest], Opts) -> 616 Which2 = 617 case Which of 618 all -> 619 all; 620 Suffixes when is_list(Suffixes) -> 621 lists:usort(Suffixes); 622 {add, Suffixes} when is_list(Suffixes) -> 623 lists:usort(Opts#zip_opts.uncompress ++ Suffixes); 624 {del, Suffixes} when is_list(Suffixes) -> 625 lists:usort(Opts#zip_opts.uncompress -- Suffixes); 626 _ -> 627 throw({bad_option, O}) 628 end, 629 get_zip_opt(Rest, Opts#zip_opts{uncompress = Which2}); 630get_zip_opt([Unknown | _Rest], _Opts) -> 631 throw({bad_option, Unknown}). 632 633 634%% feedback funs 635silent(_) -> ok. 636 637verbose_unzip(FN) -> 638 io:format("extracting: ~ts\n", [io_lib:write_string(FN)]). 639 640verbose_zip(FN) -> 641 io:format("adding: ~ts\n", [io_lib:write_string(FN)]). 642 643%% file filter funs 644all(_) -> true. 645 646file_in_list({#zip_file{name = FileName},_}, List) -> 647 lists:member(FileName, List); 648file_in_list(_, _) -> 649 false. 650 651keep_old_file({#zip_file{name = FileName},_}) -> 652 not (filelib:is_file(FileName) orelse filelib:is_dir(FileName)); 653keep_old_file(_) -> 654 false. 655 656%% fun combiner 657fun_and_1(Fun1, Fun2) -> 658 fun(A) -> Fun1(A) andalso Fun2(A) end. 659 660%% getting options 661get_zip_options(Files, Options) -> 662 Suffixes = [".Z", ".zip", ".zoo", ".arc", ".lzh", ".arj"], 663 Opts = #zip_opts{output = fun file_io/2, 664 input = get_zip_input({files, Files}), 665 open_opts = [raw, write], 666 comment = "", 667 feedback = fun silent/1, 668 cwd = "", 669 compress = all, 670 uncompress = Suffixes 671 }, 672 Opts1 = #zip_opts{comment = Comment} = get_zip_opt(Options, Opts), 673 %% UTF-8 encode characters in the interval from 127 to 255. 674 {Comment1, _} = encode_string(Comment), 675 Opts1#zip_opts{comment = Comment1}. 676 677get_unzip_options(F, Options) -> 678 Opts = #unzip_opts{file_filter = fun all/1, 679 output = fun file_io/2, 680 input = get_input(F), 681 open_opts = [raw], 682 feedback = fun silent/1, 683 cwd = "" 684 }, 685 get_unzip_opt(Options, Opts). 686 687get_openzip_options(Options) -> 688 Opts = #openzip_opts{open_opts = [raw, read], 689 output = fun file_io/2, 690 cwd = ""}, 691 get_openzip_opt(Options, Opts). 692 693get_input(F) when is_binary(F) -> 694 fun binary_io/2; 695get_input(F) when is_list(F) -> 696 fun file_io/2; 697get_input(_) -> 698 throw(einval). 699 700get_zip_input({F, B}) when is_binary(B), is_list(F) -> 701 fun binary_io/2; 702get_zip_input({F, B, #file_info{}}) when is_binary(B), is_list(F) -> 703 fun binary_io/2; 704get_zip_input({F, #file_info{}, B}) when is_binary(B), is_list(F) -> 705 fun binary_io/2; 706get_zip_input(F) when is_list(F) -> 707 fun file_io/2; 708get_zip_input({files, []}) -> 709 fun binary_io/2; 710get_zip_input({files, [File | _]}) -> 711 get_zip_input(File); 712get_zip_input(_) -> 713 throw(einval). 714 715get_list_dir_options(F, Options) -> 716 Opts = #list_dir_opts{raw_iterator = fun raw_file_info_public/5, 717 input = get_input(F), 718 open_opts = [raw]}, 719 get_list_dir_opt(Options, Opts). 720 721%% aliases for erl_tar compatibility 722-spec(table(Archive) -> RetValue when 723 Archive :: file:name() | binary(), 724 RetValue :: {ok, CommentAndFiles} | {error, Reason :: term()}, 725 CommentAndFiles :: [zip_comment() | zip_file()]). 726 727table(F) -> list_dir(F). 728 729-spec(table(Archive, Options) -> RetValue when 730 Archive :: file:name() | binary(), 731 RetValue :: {ok, CommentAndFiles} | {error, Reason :: term()}, 732 CommentAndFiles :: [zip_comment() | zip_file()], 733 734 Options :: [Option], 735 Option :: cooked). 736 737table(F, O) -> list_dir(F, O). 738 739-spec(create(Name, FileList) -> RetValue when 740 Name :: file:name(), 741 FileList :: [FileSpec], 742 FileSpec :: file:name() | {file:name(), binary()} 743 | {file:name(), binary(), file:file_info()}, 744 RetValue :: {ok, FileName :: filename()} 745 | {ok, {FileName :: filename(), binary()}} 746 | {error, Reason :: term()}). 747 748create(F, Fs) -> zip(F, Fs). 749 750-spec(create(Name, FileList, Options) -> RetValue when 751 Name :: file:name(), 752 FileList :: [FileSpec], 753 FileSpec :: file:name() | {file:name(), binary()} 754 | {file:name(), binary(), file:file_info()}, 755 Options :: [Option], 756 Option :: create_option(), 757 RetValue :: {ok, FileName :: filename()} 758 | {ok, {FileName :: filename(), binary()}} 759 | {error, Reason :: term()}). 760create(F, Fs, O) -> zip(F, Fs, O). 761 762-spec(extract(Archive) -> RetValue when 763 Archive :: file:name() | binary(), 764 RetValue :: {ok, FileList} 765 | {ok, FileBinList} 766 | {error, Reason :: term()} 767 | {error, {Name :: file:name(), Reason :: term()}}, 768 FileList :: [file:name()], 769 FileBinList :: [{file:name(),binary()}]). 770 771extract(F) -> unzip(F). 772 773-spec(extract(Archive, Options) -> RetValue when 774 Archive :: file:name() | binary(), 775 Options :: [Option], 776 Option :: {file_list, FileList} 777 | keep_old_files | verbose | memory | 778 {file_filter, FileFilter} | {cwd, CWD}, 779 FileList :: [file:name()], 780 FileBinList :: [{file:name(),binary()}], 781 FileFilter :: fun((ZipFile) -> boolean()), 782 CWD :: file:filename(), 783 ZipFile :: zip_file(), 784 RetValue :: {ok, FileList} 785 | {ok, FileBinList} 786 | {error, Reason :: term()} 787 | {error, {Name :: file:name(), Reason :: term()}}). 788 789extract(F, O) -> unzip(F, O). 790 791 792%% put the central directory, at the end of the zip archive 793put_central_dir(LHS, Pos, Out0, 794 #zip_opts{output = Output, comment = Comment}) -> 795 {Out1, Sz} = put_cd_files_loop(LHS, Output, Out0, 0), 796 put_eocd(length(LHS), Pos, Sz, Comment, Output, Out1). 797 798put_cd_files_loop([], _Output, Out, Sz) -> 799 {Out, Sz}; 800put_cd_files_loop([{LH, Name, Pos} | LHRest], Output, Out0, Sz0) -> 801 CDFH = cd_file_header_from_lh_and_pos(LH, Pos), 802 BCDFH = cd_file_header_to_bin(CDFH), 803 B = [<<?CENTRAL_FILE_MAGIC:32/little>>, BCDFH, Name], 804 Out1 = Output({write, B}, Out0), 805 Sz1 = Sz0 + ?CENTRAL_FILE_HEADER_SZ + 806 LH#local_file_header.file_name_length, 807 put_cd_files_loop(LHRest, Output, Out1, Sz1). 808 809%% put end marker of central directory, the last record in the archive 810put_eocd(N, Pos, Sz, Comment, Output, Out0) -> 811 %% BComment = list_to_binary(Comment), 812 CommentSz = length(Comment), % size(BComment), 813 EOCD = #eocd{disk_num = 0, 814 start_disk_num = 0, 815 entries_on_disk = N, 816 entries = N, 817 size = Sz, 818 offset = Pos, 819 zip_comment_length = CommentSz}, 820 BEOCD = eocd_to_bin(EOCD), 821 B = [<<?END_OF_CENTRAL_DIR_MAGIC:32/little>>, BEOCD, Comment], % BComment], 822 Output({write, B}, Out0). 823 824get_filename({Name, _}, Type) -> 825 get_filename(Name, Type); 826get_filename({Name, _, _}, Type) -> 827 get_filename(Name, Type); 828get_filename(Name, regular) -> 829 Name; 830get_filename(Name, directory) -> 831 %% Ensure trailing slash 832 case lists:reverse(Name) of 833 [$/ | _Rev] -> Name; 834 Rev -> lists:reverse([$/ | Rev]) 835 end. 836 837add_cwd(_CWD, {_Name, _} = F) -> F; 838add_cwd("", F) -> F; 839add_cwd(CWD, F) -> filename:join(CWD, F). 840 841%% already compressed data should be stored as is in archive, 842%% a simple name-match is used to check for this 843%% files smaller than 10 bytes are also stored, not compressed 844get_comp_method(_, N, _, _) when is_integer(N), N < 10 -> 845 ?STORED; 846get_comp_method(_, _, _, directory) -> 847 ?STORED; 848get_comp_method(F, _, #zip_opts{compress = Compress, uncompress = Uncompress}, _) -> 849 Ext = filename:extension(F), 850 Test = fun(Which) -> (Which =:= all) orelse lists:member(Ext, Which) end, 851 case Test(Compress) andalso not Test(Uncompress) of 852 true -> ?DEFLATED; 853 false -> ?STORED 854 end. 855 856put_z_files([], _Z, Out, Pos, _Opts, Acc) -> 857 {Out, lists:reverse(Acc, []), Pos}; 858put_z_files([F | Rest], Z, Out0, Pos0, 859 #zip_opts{input = Input, output = Output, open_opts = OpO, 860 feedback = FB, cwd = CWD} = Opts, Acc) -> 861 In0 = [], 862 F1 = add_cwd(CWD, F), 863 FileInfo = Input({file_info, F1}, In0), 864 Type = FileInfo#file_info.type, 865 UncompSize = 866 case Type of 867 regular -> FileInfo#file_info.size; 868 directory -> 0 869 end, 870 FileName0 = get_filename(F, Type), 871 %% UTF-8 encode characters in the interval from 127 to 255. 872 {FileName, GPFlag} = encode_string(FileName0), 873 CompMethod = get_comp_method(FileName, UncompSize, Opts, Type), 874 LH = local_file_header_from_info_method_name(FileInfo, UncompSize, CompMethod, FileName, GPFlag), 875 BLH = local_file_header_to_bin(LH), 876 B = [<<?LOCAL_FILE_MAGIC:32/little>>, BLH], 877 Out1 = Output({write, B}, Out0), 878 Out2 = Output({write, FileName}, Out1), 879 {Out3, CompSize, CRC} = put_z_file(CompMethod, UncompSize, Out2, F1, 880 0, Input, Output, OpO, Z, Type), 881 FB(FileName0), 882 Patch = <<CRC:32/little, CompSize:32/little>>, 883 Out4 = Output({pwrite, Pos0 + ?LOCAL_FILE_HEADER_CRC32_OFFSET, Patch}, Out3), 884 Out5 = Output({seek, eof, 0}, Out4), 885 Pos1 = Pos0 + ?LOCAL_FILE_HEADER_SZ + LH#local_file_header.file_name_length, 886 Pos2 = Pos1 + CompSize, 887 LH2 = LH#local_file_header{comp_size = CompSize, crc32 = CRC}, 888 ThisAcc = [{LH2, FileName, Pos0}], 889 {Out6, SubAcc, Pos3} = 890 case Type of 891 regular -> 892 {Out5, ThisAcc, Pos2}; 893 directory -> 894 Files = Input({list_dir, F1}, []), 895 RevFiles = reverse_join_files(F, Files, []), 896 put_z_files(RevFiles, Z, Out5, Pos2, Opts, ThisAcc) 897 end, 898 Acc2 = lists:reverse(SubAcc) ++ Acc, 899 put_z_files(Rest, Z, Out6, Pos3, Opts, Acc2). 900 901reverse_join_files(Dir, [File | Files], Acc) -> 902 reverse_join_files(Dir, Files, [filename:join([Dir, File]) | Acc]); 903reverse_join_files(_Dir, [], Acc) -> 904 Acc. 905 906%% flag for zlib 907-define(MAX_WBITS, 15). 908 909%% compress a file 910put_z_file(_Method, Sz, Out, _F, Pos, _Input, _Output, _OpO, _Z, directory) -> 911 {Out, Pos + Sz, 0}; 912put_z_file(_Method, 0, Out, _F, Pos, _Input, _Output, _OpO, _Z, regular) -> 913 {Out, Pos, 0}; 914put_z_file(?STORED, UncompSize, Out0, F, Pos0, Input, Output, OpO, Z, regular) -> 915 In0 = [], 916 In1 = Input({open, F, OpO -- [write]}, In0), 917 CRC0 = zlib:crc32(Z, <<>>), 918 {Data, In2} = Input({read, UncompSize}, In1), 919 Out1 = Output({write, Data}, Out0), 920 CRC = zlib:crc32(Z, CRC0, Data), 921 Input(close, In2), 922 {Out1, Pos0+erlang:iolist_size(Data), CRC}; 923put_z_file(?DEFLATED, UncompSize, Out0, F, Pos0, Input, Output, OpO, Z, regular) -> 924 In0 = [], 925 In1 = Input({open, F, OpO -- [write]}, In0), 926 ok = zlib:deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default), 927 {Out1, Pos1} = 928 put_z_data_loop(UncompSize, In1, Out0, Pos0, Input, Output, Z), 929 CRC = zlib:crc32(Z), 930 ok = zlib:deflateEnd(Z), 931 Input(close, In1), 932 {Out1, Pos1, CRC}. 933 934%% zlib is finished with the last chunk compressed 935get_sync(N, N) -> finish; 936get_sync(_, _) -> full. 937 938%% compress data 939put_z_data_loop(0, _In, Out, Pos, _Input, _Output, _Z) -> 940 {Out, Pos}; 941put_z_data_loop(UncompSize, In0, Out0, Pos0, Input, Output, Z) -> 942 N = erlang:min(?WRITE_BLOCK_SIZE, UncompSize), 943 case Input({read, N}, In0) of 944 {eof, _In1} -> 945 {Out0, Pos0}; 946 {Uncompressed, In1} -> 947 Compressed = zlib:deflate(Z, Uncompressed, get_sync(N, UncompSize)), 948 Sz = erlang:iolist_size(Compressed), 949 Out1 = Output({write, Compressed}, Out0), 950 put_z_data_loop(UncompSize - N, In1, Out1, Pos0 + Sz, 951 Input, Output, Z) 952 end. 953 954%% raw iterators over central dir 955 956%% name only 957raw_name_only(CD, FileName, _FileComment, _BExtraField, Acc) 958 when is_record(CD, cd_file_header) -> 959 [FileName | Acc]; 960raw_name_only(EOCD, _, _Comment, _, Acc) when is_record(EOCD, eocd) -> 961 Acc. 962 963%% for printing directory (t/1) 964raw_short_print_info_etc(CD, FileName, _FileComment, _BExtraField, Acc) 965 when is_record(CD, cd_file_header) -> 966 print_file_name(FileName), 967 Acc; 968raw_short_print_info_etc(EOCD, X, Comment, Y, Acc) when is_record(EOCD, eocd) -> 969 raw_long_print_info_etc(EOCD, X, Comment, Y, Acc). 970 971print_file_name(FileName) -> 972 io:format("~ts\n", [FileName]). 973 974 975%% for printing directory (tt/1) 976raw_long_print_info_etc(#cd_file_header{comp_size = CompSize, 977 uncomp_size = UncompSize, 978 last_mod_date = LMDate, 979 last_mod_time = LMTime}, 980 FileName, FileComment, _BExtraField, Acc) -> 981 MTime = dos_date_time_to_datetime(LMDate, LMTime), 982 print_header(CompSize, MTime, UncompSize, FileName, FileComment), 983 Acc; 984raw_long_print_info_etc(EOCD, _, Comment, _, Acc) when is_record(EOCD, eocd) -> 985 print_comment(Comment), 986 Acc. 987 988print_header(CompSize, MTime, UncompSize, FileName, FileComment) -> 989 io:format("~8w ~s ~8w ~2w% ~ts ~ts\n", 990 [CompSize, time_to_string(MTime), UncompSize, 991 get_percent(CompSize, UncompSize), FileName, FileComment]). 992 993print_comment("") -> 994 ok; 995print_comment(Comment) -> 996 io:format("Archive comment: ~ts\n", [Comment]). 997 998get_percent(_, 0) -> 100; 999get_percent(CompSize, Size) -> round(CompSize * 100 / Size). 1000 1001%% time formatting ("borrowed" from erl_tar.erl) 1002time_to_string({{Y, Mon, Day}, {H, Min, _}}) -> 1003 io_lib:format("~s ~2w ~s:~s ~w", 1004 [month(Mon), Day, two_d(H), two_d(Min), Y]). 1005 1006two_d(N) -> 1007 tl(integer_to_list(N + 100)). 1008 1009month(1) -> "Jan"; 1010month(2) -> "Feb"; 1011month(3) -> "Mar"; 1012month(4) -> "Apr"; 1013month(5) -> "May"; 1014month(6) -> "Jun"; 1015month(7) -> "Jul"; 1016month(8) -> "Aug"; 1017month(9) -> "Sep"; 1018month(10) -> "Oct"; 1019month(11) -> "Nov"; 1020month(12) -> "Dec". 1021 1022%% zip header functions 1023cd_file_header_from_lh_and_pos(LH, Pos) -> 1024 #local_file_header{version_needed = VersionNeeded, 1025 gp_flag = GPFlag, 1026 comp_method = CompMethod, 1027 last_mod_time = LastModTime, 1028 last_mod_date = LastModDate, 1029 crc32 = CRC32, 1030 comp_size = CompSize, 1031 uncomp_size = UncompSize, 1032 file_name_length = FileNameLength, 1033 extra_field_length = ExtraFieldLength, 1034 type = Type} = LH, 1035 #cd_file_header{version_made_by = ?VERSION_MADE_BY, 1036 version_needed = VersionNeeded, 1037 gp_flag = GPFlag, 1038 comp_method = CompMethod, 1039 last_mod_time = LastModTime, 1040 last_mod_date = LastModDate, 1041 crc32 = CRC32, 1042 comp_size = CompSize, 1043 uncomp_size = UncompSize, 1044 file_name_length = FileNameLength, 1045 extra_field_length = ExtraFieldLength, 1046 file_comment_length = 0, % FileCommentLength, 1047 disk_num_start = 0, % DiskNumStart, 1048 internal_attr = 0, % InternalAttr, 1049 external_attr = % ExternalAttr 1050 case Type of 1051 regular -> ?CENTRAL_REGULAR_FILE_EXT_ATTRIBUTES; 1052 directory -> ?CENTRAL_DIRECTORY_FILE_EXT_ATTRIBUTES 1053 end, 1054 local_header_offset = Pos}. 1055 1056cd_file_header_to_bin( 1057 #cd_file_header{version_made_by = VersionMadeBy, 1058 version_needed = VersionNeeded, 1059 gp_flag = GPFlag, 1060 comp_method = CompMethod, 1061 last_mod_time = LastModTime, 1062 last_mod_date = LastModDate, 1063 crc32 = CRC32, 1064 comp_size = CompSize, 1065 uncomp_size = UncompSize, 1066 file_name_length = FileNameLength, 1067 extra_field_length = ExtraFieldLength, 1068 file_comment_length = FileCommentLength, 1069 disk_num_start = DiskNumStart, 1070 internal_attr = InternalAttr, 1071 external_attr = ExternalAttr, 1072 local_header_offset = LocalHeaderOffset}) -> 1073 <<VersionMadeBy:16/little, 1074 VersionNeeded:16/little, 1075 GPFlag:16/little, 1076 CompMethod:16/little, 1077 LastModTime:16/little, 1078 LastModDate:16/little, 1079 CRC32:32/little, 1080 CompSize:32/little, 1081 UncompSize:32/little, 1082 FileNameLength:16/little, 1083 ExtraFieldLength:16/little, 1084 FileCommentLength:16/little, 1085 DiskNumStart:16/little, 1086 InternalAttr:16/little, 1087 ExternalAttr:32/little, 1088 LocalHeaderOffset:32/little>>. 1089 1090local_file_header_to_bin( 1091 #local_file_header{version_needed = VersionNeeded, 1092 gp_flag = GPFlag, 1093 comp_method = CompMethod, 1094 last_mod_time = LastModTime, 1095 last_mod_date = LastModDate, 1096 crc32 = CRC32, 1097 comp_size = CompSize, 1098 uncomp_size = UncompSize, 1099 file_name_length = FileNameLength, 1100 extra_field_length = ExtraFieldLength}) -> 1101 <<VersionNeeded:16/little, 1102 GPFlag:16/little, 1103 CompMethod:16/little, 1104 LastModTime:16/little, 1105 LastModDate:16/little, 1106 CRC32:32/little, 1107 CompSize:32/little, 1108 UncompSize:32/little, 1109 FileNameLength:16/little, 1110 ExtraFieldLength:16/little>>. 1111 1112eocd_to_bin(#eocd{disk_num = DiskNum, 1113 start_disk_num = StartDiskNum, 1114 entries_on_disk = EntriesOnDisk, 1115 entries = Entries, 1116 size = Size, 1117 offset = Offset, 1118 zip_comment_length = ZipCommentLength}) -> 1119 <<DiskNum:16/little, 1120 StartDiskNum:16/little, 1121 EntriesOnDisk:16/little, 1122 Entries:16/little, 1123 Size:32/little, 1124 Offset:32/little, 1125 ZipCommentLength:16/little>>. 1126 1127%% put together a local file header 1128local_file_header_from_info_method_name(#file_info{mtime = MTime, type = Type}, 1129 UncompSize, 1130 CompMethod, Name, GPFlag) -> 1131 {ModDate, ModTime} = dos_date_time_from_datetime(MTime), 1132 #local_file_header{version_needed = 20, 1133 gp_flag = GPFlag, 1134 comp_method = CompMethod, 1135 last_mod_time = ModTime, 1136 last_mod_date = ModDate, 1137 crc32 = -1, 1138 comp_size = -1, 1139 uncomp_size = UncompSize, 1140 file_name_length = length(Name), 1141 extra_field_length = 0, 1142 type = Type}. 1143 1144server_init(Parent) -> 1145 %% we want to know if our parent dies 1146 process_flag(trap_exit, true), 1147 server_loop(Parent, not_open). 1148 1149%% small, simple, stupid zip-archive server 1150server_loop(Parent, OpenZip) -> 1151 receive 1152 {From, {open, Archive, Options}} -> 1153 case openzip_open(Archive, Options) of 1154 {ok, NewOpenZip} -> 1155 From ! {self(), {ok, self()}}, 1156 server_loop(Parent, NewOpenZip); 1157 Error -> 1158 From ! {self(), Error} 1159 end; 1160 {From, close} -> 1161 From ! {self(), openzip_close(OpenZip)}; 1162 {From, get} -> 1163 From ! {self(), openzip_get(OpenZip)}, 1164 server_loop(Parent, OpenZip); 1165 {From, {get, FileName}} -> 1166 From ! {self(), openzip_get(FileName, OpenZip)}, 1167 server_loop(Parent, OpenZip); 1168 {From, list_dir} -> 1169 From ! {self(), openzip_list_dir(OpenZip)}, 1170 server_loop(Parent, OpenZip); 1171 {From, {list_dir, Opts}} -> 1172 From ! {self(), openzip_list_dir(OpenZip, Opts)}, 1173 server_loop(Parent, OpenZip); 1174 {From, get_state} -> 1175 From ! {self(), OpenZip}, 1176 server_loop(Parent, OpenZip); 1177 {'EXIT', Parent, Reason} -> 1178 _ = openzip_close(OpenZip), 1179 exit({parent_died, Reason}); 1180 _ -> 1181 {error, bad_msg} 1182 end. 1183 1184-spec(zip_open(Archive) -> {ok, ZipHandle} | {error, Reason} when 1185 Archive :: file:name() | binary(), 1186 ZipHandle :: handle(), 1187 Reason :: term()). 1188 1189zip_open(Archive) -> zip_open(Archive, []). 1190 1191-spec(zip_open(Archive, Options) -> {ok, ZipHandle} | {error, Reason} when 1192 Archive :: file:name() | binary(), 1193 ZipHandle :: handle(), 1194 Options :: [Option], 1195 Option :: cooked | memory | {cwd, CWD :: file:filename()}, 1196 Reason :: term()). 1197 1198zip_open(Archive, Options) -> 1199 Self = self(), 1200 Pid = spawn_link(fun() -> server_init(Self) end), 1201 request(Self, Pid, {open, Archive, Options}). 1202 1203-spec(zip_get(ZipHandle) -> {ok, [Result]} | {error, Reason} when 1204 ZipHandle :: handle(), 1205 Result :: file:name() | {file:name(), binary()}, 1206 Reason :: term()). 1207 1208zip_get(Pid) when is_pid(Pid) -> 1209 request(self(), Pid, get). 1210 1211-spec(zip_close(ZipHandle) -> ok | {error, einval} when 1212 ZipHandle :: handle()). 1213 1214zip_close(Pid) when is_pid(Pid) -> 1215 request(self(), Pid, close). 1216 1217-spec(zip_get(FileName, ZipHandle) -> {ok, Result} | {error, Reason} when 1218 FileName :: file:name(), 1219 ZipHandle :: handle(), 1220 Result :: file:name() | {file:name(), binary()}, 1221 Reason :: term()). 1222 1223zip_get(FileName, Pid) when is_pid(Pid) -> 1224 request(self(), Pid, {get, FileName}). 1225 1226-spec(zip_list_dir(ZipHandle) -> {ok, Result} | {error, Reason} when 1227 Result :: [zip_comment() | zip_file()], 1228 ZipHandle :: handle(), 1229 Reason :: term()). 1230 1231zip_list_dir(Pid) when is_pid(Pid) -> 1232 request(self(), Pid, list_dir). 1233 1234zip_list_dir(Pid, Opts) when is_pid(Pid) -> 1235 request(self(), Pid, {list_dir, Opts}). 1236 1237zip_get_state(Pid) when is_pid(Pid) -> 1238 request(self(), Pid, get_state). 1239 1240request(Self, Pid, Req) -> 1241 Pid ! {Self, Req}, 1242 receive 1243 {Pid, R} -> R 1244 end. 1245 1246zip_t(Pid) when is_pid(Pid) -> 1247 Openzip = request(self(), Pid, get_state), 1248 openzip_t(Openzip). 1249 1250zip_tt(Pid) when is_pid(Pid) -> 1251 Openzip = request(self(), Pid, get_state), 1252 openzip_tt(Openzip). 1253 1254openzip_tt(#openzip{zip_comment = ZipComment, files = Files}) -> 1255 print_comment(ZipComment), 1256 lists_foreach(fun({#zip_file{comp_size = CompSize, 1257 name = FileName, 1258 comment = FileComment, 1259 info = FI},_}) -> 1260 #file_info{size = UncompSize, mtime = MTime} = FI, 1261 print_header(CompSize, MTime, UncompSize, 1262 FileName, FileComment) 1263 end, Files), 1264 ok. 1265 1266openzip_t(#openzip{zip_comment = ZipComment, files = Files}) -> 1267 print_comment(ZipComment), 1268 lists_foreach(fun({#zip_file{name = FileName},_}) -> 1269 print_file_name(FileName) 1270 end, Files), 1271 ok. 1272 1273lists_foreach(_, []) -> 1274 ok; 1275lists_foreach(F, [Hd|Tl]) -> 1276 F(Hd), 1277 lists_foreach(F, Tl). 1278 1279%% option utils 1280get_openzip_opt([], Opts) -> 1281 Opts; 1282get_openzip_opt([cooked | Rest], #openzip_opts{open_opts = OO} = Opts) -> 1283 get_openzip_opt(Rest, Opts#openzip_opts{open_opts = OO -- [raw]}); 1284get_openzip_opt([memory | Rest], Opts) -> 1285 get_openzip_opt(Rest, Opts#openzip_opts{output = fun binary_io/2}); 1286get_openzip_opt([{cwd, CWD} | Rest], Opts) -> 1287 get_openzip_opt(Rest, Opts#openzip_opts{cwd = CWD}); 1288get_openzip_opt([Unknown | _Rest], _Opts) -> 1289 throw({bad_option, Unknown}). 1290 1291%% get the central directory from the archive 1292get_central_dir(In0, RawIterator, Input) -> 1293 {B, In1} = get_end_of_central_dir(In0, ?END_OF_CENTRAL_DIR_SZ, Input), 1294 {EOCD, BComment} = eocd_and_comment_from_bin(B), 1295 In2 = Input({seek, bof, EOCD#eocd.offset}, In1), 1296 N = EOCD#eocd.entries, 1297 Acc0 = [], 1298 %% There is no encoding flag for the archive comment. 1299 Comment = heuristic_to_string(BComment), 1300 Out0 = RawIterator(EOCD, "", Comment, <<>>, Acc0), 1301 get_cd_loop(N, In2, RawIterator, Input, Out0). 1302 1303get_cd_loop(0, In, _RawIterator, _Input, Acc) -> 1304 {lists:reverse(Acc), In}; 1305get_cd_loop(N, In0, RawIterator, Input, Acc0) -> 1306 {B, In1} = Input({read, ?CENTRAL_FILE_HEADER_SZ}, In0), 1307 BCD = case B of 1308 <<?CENTRAL_FILE_MAGIC:32/little, XBCD/binary>> -> XBCD; 1309 _ -> throw(bad_central_directory) 1310 end, 1311 CD = cd_file_header_from_bin(BCD), 1312 FileNameLen = CD#cd_file_header.file_name_length, 1313 ExtraLen = CD#cd_file_header.extra_field_length, 1314 CommentLen = CD#cd_file_header.file_comment_length, 1315 ToRead = FileNameLen + ExtraLen + CommentLen, 1316 GPFlag = CD#cd_file_header.gp_flag, 1317 {B2, In2} = Input({read, ToRead}, In1), 1318 {FileName, Comment, BExtra} = 1319 get_name_extra_comment(B2, FileNameLen, ExtraLen, CommentLen, GPFlag), 1320 Acc1 = RawIterator(CD, FileName, Comment, BExtra, Acc0), 1321 get_cd_loop(N-1, In2, RawIterator, Input, Acc1). 1322 1323get_name_extra_comment(B, FileNameLen, ExtraLen, CommentLen, GPFlag) -> 1324 try 1325 <<BFileName:FileNameLen/binary, 1326 BExtra:ExtraLen/binary, 1327 BComment:CommentLen/binary>> = B, 1328 {binary_to_chars(BFileName, GPFlag), 1329 %% Appendix D says: "If general purpose bit 11 is unset, the 1330 %% file name and comment should conform to the original ZIP 1331 %% character encoding." However, it seems that at least Linux 1332 %% zip(1) encodes the comment without setting bit 11 if the 1333 %% filename is 7-bit ASCII. If bit 11 is set, 1334 %% binary_to_chars/1 could (should?) be called (it can fail), 1335 %% but the choice is to employ heuristics in this case too 1336 %% (it does not fail). 1337 heuristic_to_string(BComment), 1338 BExtra} 1339 catch 1340 _:_ -> 1341 throw(bad_central_directory) 1342 end. 1343 1344%% get end record, containing the offset to the central directory 1345%% the end record is always at the end of the file BUT alas it is 1346%% of variable size (yes that's dumb!) 1347get_end_of_central_dir(_In, Sz, _Input) when Sz > 16#ffff -> 1348 throw(bad_eocd); 1349get_end_of_central_dir(In0, Sz, Input) -> 1350 In1 = Input({seek, eof, -Sz}, In0), 1351 {B, In2} = Input({read, Sz}, In1), 1352 case find_eocd_header(B) of 1353 none -> 1354 get_end_of_central_dir(In2, Sz+Sz, Input); 1355 Header -> 1356 {Header, In2} 1357 end. 1358 1359%% find the end record by matching for it 1360find_eocd_header(<<?END_OF_CENTRAL_DIR_MAGIC:32/little, Rest/binary>>) -> 1361 Rest; 1362find_eocd_header(<<_:8, Rest/binary>>) 1363 when byte_size(Rest) > ?END_OF_CENTRAL_DIR_SZ-4 -> 1364 find_eocd_header(Rest); 1365find_eocd_header(_) -> 1366 none. 1367 1368%% from a central directory record, filter and accumulate what we need 1369 1370%% with zip_file_extra 1371raw_file_info_etc(CD, FileName, FileComment, BExtraField, Acc) 1372 when is_record(CD, cd_file_header) -> 1373 #cd_file_header{comp_size = CompSize, 1374 local_header_offset = Offset, 1375 crc32 = CRC} = CD, 1376 FileInfo = cd_file_header_to_file_info(FileName, CD, BExtraField), 1377 [{#zip_file{name = FileName, info = FileInfo, comment = FileComment, 1378 offset = Offset, comp_size = CompSize}, #zip_file_extra{crc32 = CRC}} | Acc]; 1379raw_file_info_etc(EOCD, _, Comment, _, Acc) when is_record(EOCD, eocd) -> 1380 [#zip_comment{comment = Comment} | Acc]. 1381 1382%% without zip_file_extra 1383raw_file_info_public(CD, FileName, FileComment, BExtraField, Acc0) -> 1384 [H1|T] = raw_file_info_etc(CD,FileName,FileComment,BExtraField,Acc0), 1385 H2 = case H1 of 1386 {ZF,Extra} when is_record(Extra,zip_file_extra) -> ZF; 1387 Other -> Other 1388 end, 1389 [H2|T]. 1390 1391 1392%% make a file_info from a central directory header 1393cd_file_header_to_file_info(FileName, 1394 #cd_file_header{uncomp_size = UncompSize, 1395 last_mod_time = ModTime, 1396 last_mod_date = ModDate}, 1397 ExtraField) -> 1398 T = dos_date_time_to_datetime(ModDate, ModTime), 1399 Type = 1400 case lists:last(FileName) of 1401 $/ -> directory; 1402 _ -> regular 1403 end, 1404 FI = #file_info{size = UncompSize, 1405 type = Type, 1406 access = read_write, 1407 atime = T, 1408 mtime = T, 1409 ctime = T, 1410 mode = 8#066, 1411 links = 1, 1412 major_device = 0, 1413 minor_device = 0, 1414 inode = 0, 1415 uid = 0, 1416 gid = 0}, 1417 add_extra_info(FI, ExtraField). 1418 1419%% Currently, we ignore all the extra fields. 1420add_extra_info(FI, _) -> 1421 FI. 1422 1423 1424 1425%% get all files using file list 1426%% (the offset list is already filtered on which file to get... isn't it?) 1427get_z_files([], _Z, _In, _Opts, Acc) -> 1428 lists:reverse(Acc); 1429get_z_files([#zip_comment{comment = _} | Rest], Z, In, Opts, Acc) -> 1430 get_z_files(Rest, Z, In, Opts, Acc); 1431get_z_files([{#zip_file{offset = Offset},_} = ZFile | Rest], Z, In0, 1432 #unzip_opts{input = Input, output = Output, open_opts = OpO, 1433 file_filter = Filter, feedback = FB, 1434 cwd = CWD} = Opts, Acc0) -> 1435 case Filter(ZFile) of 1436 true -> 1437 In1 = Input({seek, bof, Offset}, In0), 1438 {In2, Acc1} = 1439 case get_z_file(In1, Z, Input, Output, OpO, FB, 1440 CWD, ZFile, Filter) of 1441 {file, GZD, Inx} -> {Inx, [GZD | Acc0]}; 1442 {_, Inx} -> {Inx, Acc0} 1443 end, 1444 get_z_files(Rest, Z, In2, Opts, Acc1); 1445 _ -> 1446 get_z_files(Rest, Z, In0, Opts, Acc0) 1447 end. 1448 1449%% get a file from the archive, reading chunks 1450get_z_file(In0, Z, Input, Output, OpO, FB, 1451 CWD, {ZipFile,Extra}, Filter) -> 1452 case Input({read, ?LOCAL_FILE_HEADER_SZ}, In0) of 1453 {eof, In1} -> 1454 {eof, In1}; 1455 %% Local File Header 1456 {<<?LOCAL_FILE_MAGIC:32/little, B/binary>>, In1} -> 1457 LH = local_file_header_from_bin(B), 1458 #local_file_header{gp_flag = GPFlag, 1459 comp_method = CompMethod, 1460 file_name_length = FileNameLen, 1461 extra_field_length = ExtraLen} = LH, 1462 1463 {CompSize,CRC32} = case GPFlag band 8 =:= 8 of 1464 true -> {ZipFile#zip_file.comp_size, 1465 Extra#zip_file_extra.crc32}; 1466 false -> {LH#local_file_header.comp_size, 1467 LH#local_file_header.crc32} 1468 end, 1469 {BFileN, In3} = Input({read, FileNameLen + ExtraLen}, In1), 1470 {FileName, _} = 1471 get_file_name_extra(FileNameLen, ExtraLen, BFileN, GPFlag), 1472 ReadAndWrite = 1473 case check_valid_location(CWD, FileName) of 1474 {true,FileName1} -> 1475 true; 1476 {false,FileName1} -> 1477 Filter({ZipFile#zip_file{name = FileName1},Extra}) 1478 end, 1479 case ReadAndWrite of 1480 true -> 1481 case lists:last(FileName) of 1482 $/ -> 1483 %% perhaps this should always be done? 1484 Output({ensure_dir,FileName1},[]), 1485 {dir, In3}; 1486 _ -> 1487 %% FileInfo = local_file_header_to_file_info(LH) 1488 %%{Out, In4, CRC, UncompSize} = 1489 {Out, In4, CRC, _UncompSize} = 1490 get_z_data(CompMethod, In3, FileName1, 1491 CompSize, Input, Output, OpO, Z), 1492 In5 = skip_z_data_descriptor(GPFlag, Input, In4), 1493 %% TODO This should be fixed some day: 1494 %% In5 = Input({set_file_info, FileName, 1495 %% FileInfo#file_info{size=UncompSize}}, In4), 1496 FB(FileName), 1497 CRC =:= CRC32 orelse throw({bad_crc, FileName}), 1498 {file, Out, In5} 1499 end; 1500 false -> 1501 {ignore, In3} 1502 end; 1503 _ -> 1504 throw(bad_local_file_header) 1505 end. 1506 1507%% make sure FileName doesn't have relative path that points over CWD 1508check_valid_location(CWD, FileName) -> 1509 %% check for directory traversal exploit 1510 case check_dir_level(filename:split(FileName), 0) of 1511 {FileOrDir,Level} when Level < 0 -> 1512 CWD1 = if CWD == "" -> "./"; 1513 true -> CWD 1514 end, 1515 error_logger:format("Illegal path: ~ts, extracting in ~ts~n", 1516 [add_cwd(CWD,FileName),CWD1]), 1517 {false,add_cwd(CWD, FileOrDir)}; 1518 _ -> 1519 {true,add_cwd(CWD, FileName)} 1520 end. 1521 1522check_dir_level([FileOrDir], Level) -> 1523 {FileOrDir,Level}; 1524check_dir_level(["." | Parts], Level) -> 1525 check_dir_level(Parts, Level); 1526check_dir_level([".." | Parts], Level) -> 1527 check_dir_level(Parts, Level-1); 1528check_dir_level([_Dir | Parts], Level) -> 1529 check_dir_level(Parts, Level+1). 1530 1531get_file_name_extra(FileNameLen, ExtraLen, B, GPFlag) -> 1532 try 1533 <<BFileName:FileNameLen/binary, BExtra:ExtraLen/binary>> = B, 1534 {binary_to_chars(BFileName, GPFlag), BExtra} 1535 catch 1536 _:_ -> 1537 throw(bad_file_header) 1538 end. 1539 1540%% get compressed or stored data 1541get_z_data(?DEFLATED, In0, FileName, CompSize, Input, Output, OpO, Z) -> 1542 ok = zlib:inflateInit(Z, -?MAX_WBITS), 1543 Out0 = Output({open, FileName, [write | OpO]}, []), 1544 {In1, Out1, UncompSize} = get_z_data_loop(CompSize, 0, In0, Out0, Input, Output, Z), 1545 CRC = zlib:crc32(Z), 1546 ?CATCH zlib:inflateEnd(Z), 1547 Out2 = Output({close, FileName}, Out1), 1548 {Out2, In1, CRC, UncompSize}; 1549get_z_data(?STORED, In0, FileName, CompSize, Input, Output, OpO, Z) -> 1550 Out0 = Output({open, FileName, [write | OpO]}, []), 1551 CRC0 = zlib:crc32(Z, <<>>), 1552 {In1, Out1, CRC} = copy_data_loop(CompSize, In0, Out0, Input, Output, 1553 CRC0, Z), 1554 Out2 = Output({close, FileName}, Out1), 1555 {Out2, In1, CRC, CompSize}; 1556get_z_data(_, _, _, _, _, _, _, _) -> 1557 throw(bad_file_header). 1558 1559copy_data_loop(0, In, Out, _Input, _Output, CRC, _Z) -> 1560 {In, Out, CRC}; 1561copy_data_loop(CompSize, In0, Out0, Input, Output, CRC0, Z) -> 1562 N = erlang:min(?READ_BLOCK_SIZE, CompSize), 1563 case Input({read, N}, In0) of 1564 {eof, In1} -> {Out0, In1}; 1565 {Uncompressed, In1} -> 1566 CRC1 = zlib:crc32(Z, CRC0, Uncompressed), 1567 Out1 = Output({write, Uncompressed}, Out0), 1568 copy_data_loop(CompSize-N, In1, Out1, Input, Output, CRC1, Z) 1569 end. 1570 1571get_z_data_loop(0, UncompSize, In, Out, _Input, _Output, _Z) -> 1572 {In, Out, UncompSize}; 1573get_z_data_loop(CompSize, UncompSize, In0, Out0, Input, Output, Z) -> 1574 N = erlang:min(?READ_BLOCK_SIZE, CompSize), 1575 case Input({read, N}, In0) of 1576 {eof, In1} -> 1577 {Out0, In1}; 1578 {Compressed, In1} -> 1579 Uncompressed = zlib:inflate(Z, Compressed), 1580 Out1 = Output({write, Uncompressed}, Out0), 1581 get_z_data_loop(CompSize-N, UncompSize + iolist_size(Uncompressed), 1582 In1, Out1, Input, Output, Z) 1583 end. 1584 1585 1586%% skip data descriptor if any 1587skip_z_data_descriptor(GPFlag, Input, In0) when GPFlag band 8 =:= 8 -> 1588 Input({seek, cur, 12}, In0); 1589skip_z_data_descriptor(_GPFlag, _Input, In0) -> 1590 In0. 1591 1592%% convert between erlang datetime and the MSDOS date and time 1593%% that's stored in the zip archive 1594%% MSDOS Time MSDOS Date 1595%% bit 0 - 4 5 - 10 11 - 15 16 - 20 21 - 24 25 - 31 1596%% value second minute hour day (1 - 31) month (1 - 12) years from 1980 1597dos_date_time_to_datetime(DosDate, DosTime) -> 1598 <<Hour:5, Min:6, Sec:5>> = <<DosTime:16>>, 1599 <<YearFrom1980:7, Month:4, Day:5>> = <<DosDate:16>>, 1600 {{YearFrom1980+1980, Month, Day}, 1601 {Hour, Min, Sec}}. 1602 1603dos_date_time_from_datetime({{Year, Month, Day}, {Hour, Min, Sec}}) -> 1604 YearFrom1980 = Year-1980, 1605 <<DosTime:16>> = <<Hour:5, Min:6, Sec:5>>, 1606 <<DosDate:16>> = <<YearFrom1980:7, Month:4, Day:5>>, 1607 {DosDate, DosTime}. 1608 1609%% A pwrite-like function for iolists (used by memory-option) 1610 1611pwrite_binary(B, Pos, Bin) when byte_size(B) =:= Pos -> 1612 append_bins(Bin, B); 1613pwrite_binary(B, Pos, Bin) -> 1614 erlang:iolist_to_binary(pwrite_iolist(B, Pos, Bin)). 1615 1616append_bins([Bin|Bins], B) when is_binary(Bin) -> 1617 append_bins(Bins, <<B/binary, Bin/binary>>); 1618append_bins([List|Bins], B) when is_list(List) -> 1619 append_bins(Bins, append_bins(List, B)); 1620append_bins(Bin, B) when is_binary(Bin) -> 1621 <<B/binary, Bin/binary>>; 1622append_bins([_|_]=List, B) -> 1623 <<B/binary, (iolist_to_binary(List))/binary>>; 1624append_bins([], B) -> 1625 B. 1626 1627-dialyzer({no_improper_lists, pwrite_iolist/3}). 1628 1629pwrite_iolist(B, Pos, Bin) -> 1630 {Left, Right} = split_binary(B, Pos), 1631 Sz = erlang:iolist_size(Bin), 1632 R = skip_bin(Right, Sz), 1633 [Left, Bin | R]. 1634 1635skip_bin(B, Pos) when is_binary(B) -> 1636 case B of 1637 <<_:Pos/binary, Bin/binary>> -> Bin; 1638 _ -> <<>> 1639 end. 1640 1641binary_to_chars(B, GPFlag) -> 1642 ?SHOW_GP_BIT_11(B, GPFlag band ?GP_BIT_11), 1643 case GPFlag band ?GP_BIT_11 of 1644 0 -> 1645 binary_to_list(B); 1646 ?GP_BIT_11 -> 1647 case unicode:characters_to_list(B) of 1648 List when is_list(List) -> 1649 List 1650 end 1651 end. 1652 1653heuristic_to_string(B) when is_binary(B) -> 1654 case unicode:characters_to_binary(B) of 1655 B -> 1656 unicode:characters_to_list(B); 1657 _ -> 1658 binary_to_list(B) 1659 end. 1660 1661encode_string(String) -> 1662 case lists:any(fun(C) -> C > 127 end, String) of 1663 true -> 1664 case unicode:characters_to_binary(String) of 1665 B when is_binary(B) -> 1666 {binary_to_list(B), ?GP_BIT_11}; 1667 _ -> 1668 throw({bad_unicode, String}) 1669 end; 1670 false -> 1671 {String, 0} 1672 end. 1673 1674%% ZIP header manipulations 1675eocd_and_comment_from_bin(<<DiskNum:16/little, 1676 StartDiskNum:16/little, 1677 EntriesOnDisk:16/little, 1678 Entries:16/little, 1679 Size:32/little, 1680 Offset:32/little, 1681 ZipCommentLength:16/little, 1682 Comment:ZipCommentLength/binary>>) -> 1683 {#eocd{disk_num = DiskNum, 1684 start_disk_num = StartDiskNum, 1685 entries_on_disk = EntriesOnDisk, 1686 entries = Entries, 1687 size = Size, 1688 offset = Offset, 1689 zip_comment_length = ZipCommentLength}, 1690 Comment}; 1691eocd_and_comment_from_bin(_) -> 1692 throw(bad_eocd). 1693 1694cd_file_header_from_bin(<<VersionMadeBy:16/little, 1695 VersionNeeded:16/little, 1696 GPFlag:16/little, 1697 CompMethod:16/little, 1698 LastModTime:16/little, 1699 LastModDate:16/little, 1700 CRC32:32/little, 1701 CompSize:32/little, 1702 UncompSize:32/little, 1703 FileNameLength:16/little, 1704 ExtraFieldLength:16/little, 1705 FileCommentLength:16/little, 1706 DiskNumStart:16/little, 1707 InternalAttr:16/little, 1708 ExternalAttr:32/little, 1709 LocalHeaderOffset:32/little>>) -> 1710 #cd_file_header{version_made_by = VersionMadeBy, 1711 version_needed = VersionNeeded, 1712 gp_flag = GPFlag, 1713 comp_method = CompMethod, 1714 last_mod_time = LastModTime, 1715 last_mod_date = LastModDate, 1716 crc32 = CRC32, 1717 comp_size = CompSize, 1718 uncomp_size = UncompSize, 1719 file_name_length = FileNameLength, 1720 extra_field_length = ExtraFieldLength, 1721 file_comment_length = FileCommentLength, 1722 disk_num_start = DiskNumStart, 1723 internal_attr = InternalAttr, 1724 external_attr = ExternalAttr, 1725 local_header_offset = LocalHeaderOffset}; 1726cd_file_header_from_bin(_) -> 1727 throw(bad_cd_file_header). 1728 1729local_file_header_from_bin(<<VersionNeeded:16/little, 1730 GPFlag:16/little, 1731 CompMethod:16/little, 1732 LastModTime:16/little, 1733 LastModDate:16/little, 1734 CRC32:32/little, 1735 CompSize:32/little, 1736 UncompSize:32/little, 1737 FileNameLength:16/little, 1738 ExtraFieldLength:16/little>>) -> 1739 #local_file_header{version_needed = VersionNeeded, 1740 gp_flag = GPFlag, 1741 comp_method = CompMethod, 1742 last_mod_time = LastModTime, 1743 last_mod_date = LastModDate, 1744 crc32 = CRC32, 1745 comp_size = CompSize, 1746 uncomp_size = UncompSize, 1747 file_name_length = FileNameLength, 1748 extra_field_length = ExtraFieldLength}; 1749local_file_header_from_bin(_) -> 1750 throw(bad_local_file_header). 1751 1752%% make a file_info from a local directory header 1753%% local_file_header_to_file_info( 1754%% #local_file_header{last_mod_time = ModTime, 1755%% last_mod_date = ModDate, 1756%% uncomp_size = UncompSize}) -> 1757%% T = dos_date_time_to_datetime(ModDate, ModTime), 1758%% FI = #file_info{size = UncompSize, 1759%% type = regular, 1760%% access = read_write, 1761%% atime = T, 1762%% mtime = T, 1763%% ctime = T, 1764%% mode = 8#066, 1765%% links = 1, 1766%% major_device = 0, 1767%% minor_device = 0, 1768%% inode = 0, 1769%% uid = 0, 1770%% gid = 0}, 1771%% FI. 1772 1773%% io functions 1774binary_io({file_info, {_Filename, _B, #file_info{} = FI}}, _A) -> 1775 FI; 1776binary_io({file_info, {_Filename, #file_info{} = FI, _B}}, _A) -> 1777 FI; 1778binary_io({file_info, {_Filename, B}}, A) -> 1779 binary_io({file_info, B}, A); 1780binary_io({file_info, B}, _) -> 1781 {Type, Size} = 1782 if 1783 is_binary(B) -> {regular, byte_size(B)}; 1784 B =:= directory -> {directory, 0} 1785 end, 1786 Now = calendar:local_time(), 1787 #file_info{size = Size, type = Type, 1788 access = read_write, atime = Now, 1789 mtime = Now, ctime = Now, mode = 0, 1790 links = 1, major_device = 0, 1791 minor_device = 0, inode = 0, 1792 uid = 0, gid = 0}; 1793binary_io({open, {_Filename, B, _FI}, _Opts}, _) when is_binary(B) -> 1794 {0, B}; 1795binary_io({open, {_Filename, _FI, B}, _Opts}, _) when is_binary(B) -> 1796 {0, B}; 1797binary_io({open, {_Filename, B}, _Opts}, _) when is_binary(B) -> 1798 {0, B}; 1799binary_io({open, B, _Opts}, _) when is_binary(B) -> 1800 {0, B}; 1801binary_io({open, Filename, _Opts}, _) when is_list(Filename) -> 1802 {0, <<>>}; 1803binary_io({read, N}, {Pos, B}) when Pos >= byte_size(B) -> 1804 {eof, {Pos+N, B}}; 1805binary_io({read, N}, {Pos, B}) when Pos + N > byte_size(B) -> 1806 <<_:Pos/binary, Read/binary>> = B, 1807 {Read, {byte_size(B), B}}; 1808binary_io({pread, Pos, N}, {OldPos, B}) -> 1809 case B of 1810 <<_:Pos/binary, Read:N/binary, _Rest/binary>> -> 1811 {Read, {Pos+N, B}}; 1812 _ -> 1813 {eof, {OldPos, B}} 1814 end; 1815binary_io({read, N}, {Pos, B}) -> 1816 <<_:Pos/binary, Read:N/binary, _/binary>> = B, 1817 {Read, {Pos+N, B}}; 1818binary_io({seek, bof, Pos}, {_OldPos, B}) -> 1819 {Pos, B}; 1820binary_io({seek, cur, Pos}, {OldPos, B}) -> 1821 {OldPos + Pos, B}; 1822binary_io({seek, eof, Pos}, {_OldPos, B}) -> 1823 {byte_size(B) + Pos, B}; 1824binary_io({pwrite, Pos, Data}, {OldPos, B}) -> 1825 {OldPos, pwrite_binary(B, Pos, Data)}; 1826binary_io({write, Data}, {Pos, B}) -> 1827 {Pos + erlang:iolist_size(Data), pwrite_binary(B, Pos, Data)}; 1828binary_io(close, {_Pos, B}) -> 1829 B; 1830binary_io({close, FN}, {_Pos, B}) -> 1831 {FN, B}; 1832binary_io({list_dir, _F}, _B) -> 1833 []; 1834binary_io({set_file_info, _F, _FI}, B) -> 1835 B; 1836binary_io({ensure_dir, _Dir}, B) -> 1837 B. 1838 1839file_io({file_info, F}, _) -> 1840 case file:read_file_info(F) of 1841 {ok, Info} -> Info; 1842 {error, E} -> throw(E) 1843 end; 1844file_io({open, FN, Opts}, _) -> 1845 case lists:member(write, Opts) of 1846 true -> ok = filelib:ensure_dir(FN); 1847 _ -> ok 1848 end, 1849 case file:open(FN, Opts++[binary]) of 1850 {ok, H} -> H; 1851 {error, E} -> throw(E) 1852 end; 1853file_io({read, N}, H) -> 1854 case file:read(H, N) of 1855 {ok, B} -> {B, H}; 1856 eof -> {eof, H}; 1857 {error, E} -> throw(E) 1858 end; 1859file_io({pread, Pos, N}, H) -> 1860 case file:pread(H, Pos, N) of 1861 {ok, B} -> {B, H}; 1862 eof -> {eof, H}; 1863 {error, E} -> throw(E) 1864 end; 1865file_io({seek, S, Pos}, H) -> 1866 case file:position(H, {S, Pos}) of 1867 {ok, _NewPos} -> H; 1868 {error, Error} -> throw(Error) 1869 end; 1870file_io({write, Data}, H) -> 1871 case file:write(H, Data) of 1872 ok -> H; 1873 {error, Error} -> throw(Error) 1874 end; 1875file_io({pwrite, Pos, Data}, H) -> 1876 case file:pwrite(H, Pos, Data) of 1877 ok -> H; 1878 {error, Error} -> throw(Error) 1879 end; 1880file_io({close, FN}, H) -> 1881 case file:close(H) of 1882 ok -> FN; 1883 {error, Error} -> throw(Error) 1884 end; 1885file_io(close, H) -> 1886 file_io({close, ok}, H); 1887file_io({list_dir, F}, _H) -> 1888 case file:list_dir(F) of 1889 {ok, Files} -> Files; 1890 {error, Error} -> throw(Error) 1891 end; 1892file_io({set_file_info, F, FI}, H) -> 1893 case file:write_file_info(F, FI) of 1894 ok -> H; 1895 {error, Error} -> throw(Error) 1896 end; 1897file_io({ensure_dir, Dir}, H) -> 1898 ok = filelib:ensure_dir(Dir), 1899 H. 1900