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