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 {Data, In2} = Input({read, UncompSize}, In1), 918 Out1 = Output({write, Data}, Out0), 919 CRC = erlang:crc32(Data), 920 Input(close, In2), 921 {Out1, Pos0+erlang:iolist_size(Data), CRC}; 922put_z_file(?DEFLATED, UncompSize, Out0, F, Pos0, Input, Output, OpO, Z, regular) -> 923 In0 = [], 924 In1 = Input({open, F, OpO -- [write]}, In0), 925 ok = zlib:deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default), 926 CRC0 = 0, 927 {Out1, Pos1, CRC} = 928 put_z_data_loop(UncompSize, In1, Out0, Pos0, Input, Output, CRC0, Z), 929 ok = zlib:deflateEnd(Z), 930 Input(close, In1), 931 {Out1, Pos1, CRC}. 932 933%% zlib is finished with the last chunk compressed 934get_sync(N, N) -> finish; 935get_sync(_, _) -> full. 936 937%% compress data 938put_z_data_loop(0, _In, Out, Pos, _Input, _Output, CRC0, _Z) -> 939 {Out, Pos, CRC0}; 940put_z_data_loop(UncompSize, In0, Out0, Pos0, Input, Output, CRC0, Z) -> 941 N = erlang:min(?WRITE_BLOCK_SIZE, UncompSize), 942 case Input({read, N}, In0) of 943 {eof, _In1} -> 944 {Out0, Pos0}; 945 {Uncompressed, In1} -> 946 CRC1 = erlang:crc32(CRC0, Uncompressed), 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, CRC1, 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 CRC0 = 0, 1545 {In1, Out1, UncompSize, CRC} = get_z_data_loop(CompSize, 0, In0, Out0, Input, Output, CRC0, 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 = 0, 1552 {In1, Out1, CRC} = copy_data_loop(CompSize, In0, Out0, Input, Output, CRC0), 1553 Out2 = Output({close, FileName}, Out1), 1554 {Out2, In1, CRC, CompSize}; 1555get_z_data(_, _, _, _, _, _, _, _) -> 1556 throw(bad_file_header). 1557 1558copy_data_loop(0, In, Out, _Input, _Output, CRC) -> 1559 {In, Out, CRC}; 1560copy_data_loop(CompSize, In0, Out0, Input, Output, CRC0) -> 1561 N = erlang:min(?READ_BLOCK_SIZE, CompSize), 1562 case Input({read, N}, In0) of 1563 {eof, In1} -> 1564 {Out0, In1}; 1565 {Uncompressed, In1} -> 1566 CRC1 = erlang:crc32(CRC0, Uncompressed), 1567 Out1 = Output({write, Uncompressed}, Out0), 1568 copy_data_loop(CompSize-N, In1, Out1, Input, Output, CRC1) 1569 end. 1570 1571get_z_data_loop(0, UncompSize, In, Out, _Input, _Output, CRC0, _Z) -> 1572 {In, Out, UncompSize, CRC0}; 1573get_z_data_loop(CompSize, UncompSize, In0, Out0, Input, Output, CRC0, 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 CRC1 = erlang:crc32(CRC0, Uncompressed), 1581 Out1 = Output({write, Uncompressed}, Out0), 1582 get_z_data_loop(CompSize-N, UncompSize + iolist_size(Uncompressed), 1583 In1, Out1, Input, Output, CRC1, Z) 1584 end. 1585 1586 1587%% skip data descriptor if any 1588skip_z_data_descriptor(GPFlag, Input, In0) when GPFlag band 8 =:= 8 -> 1589 Input({seek, cur, 12}, In0); 1590skip_z_data_descriptor(_GPFlag, _Input, In0) -> 1591 In0. 1592 1593%% convert between erlang datetime and the MSDOS date and time 1594%% that's stored in the zip archive 1595%% MSDOS Time MSDOS Date 1596%% bit 0 - 4 5 - 10 11 - 15 16 - 20 21 - 24 25 - 31 1597%% value second minute hour day (1 - 31) month (1 - 12) years from 1980 1598dos_date_time_to_datetime(DosDate, DosTime) -> 1599 <<Hour:5, Min:6, Sec:5>> = <<DosTime:16>>, 1600 <<YearFrom1980:7, Month:4, Day:5>> = <<DosDate:16>>, 1601 {{YearFrom1980+1980, Month, Day}, 1602 {Hour, Min, Sec}}. 1603 1604dos_date_time_from_datetime({{Year, Month, Day}, {Hour, Min, Sec}}) -> 1605 YearFrom1980 = Year-1980, 1606 <<DosTime:16>> = <<Hour:5, Min:6, Sec:5>>, 1607 <<DosDate:16>> = <<YearFrom1980:7, Month:4, Day:5>>, 1608 {DosDate, DosTime}. 1609 1610%% A pwrite-like function for iolists (used by memory-option) 1611 1612pwrite_binary(B, Pos, Bin) when byte_size(B) =:= Pos -> 1613 append_bins(Bin, B); 1614pwrite_binary(B, Pos, Bin) -> 1615 erlang:iolist_to_binary(pwrite_iolist(B, Pos, Bin)). 1616 1617append_bins([Bin|Bins], B) when is_binary(Bin) -> 1618 append_bins(Bins, <<B/binary, Bin/binary>>); 1619append_bins([List|Bins], B) when is_list(List) -> 1620 append_bins(Bins, append_bins(List, B)); 1621append_bins(Bin, B) when is_binary(Bin) -> 1622 <<B/binary, Bin/binary>>; 1623append_bins([_|_]=List, B) -> 1624 <<B/binary, (iolist_to_binary(List))/binary>>; 1625append_bins([], B) -> 1626 B. 1627 1628-dialyzer({no_improper_lists, pwrite_iolist/3}). 1629 1630pwrite_iolist(B, Pos, Bin) -> 1631 {Left, Right} = split_binary(B, Pos), 1632 Sz = erlang:iolist_size(Bin), 1633 R = skip_bin(Right, Sz), 1634 [Left, Bin | R]. 1635 1636skip_bin(B, Pos) when is_binary(B) -> 1637 case B of 1638 <<_:Pos/binary, Bin/binary>> -> Bin; 1639 _ -> <<>> 1640 end. 1641 1642binary_to_chars(B, GPFlag) -> 1643 ?SHOW_GP_BIT_11(B, GPFlag band ?GP_BIT_11), 1644 case GPFlag band ?GP_BIT_11 of 1645 0 -> 1646 binary_to_list(B); 1647 ?GP_BIT_11 -> 1648 case unicode:characters_to_list(B) of 1649 List when is_list(List) -> 1650 List 1651 end 1652 end. 1653 1654heuristic_to_string(B) when is_binary(B) -> 1655 case unicode:characters_to_binary(B) of 1656 B -> 1657 unicode:characters_to_list(B); 1658 _ -> 1659 binary_to_list(B) 1660 end. 1661 1662encode_string(String) -> 1663 case lists:any(fun(C) -> C > 127 end, String) of 1664 true -> 1665 case unicode:characters_to_binary(String) of 1666 B when is_binary(B) -> 1667 {binary_to_list(B), ?GP_BIT_11}; 1668 _ -> 1669 throw({bad_unicode, String}) 1670 end; 1671 false -> 1672 {String, 0} 1673 end. 1674 1675%% ZIP header manipulations 1676eocd_and_comment_from_bin(<<DiskNum:16/little, 1677 StartDiskNum:16/little, 1678 EntriesOnDisk:16/little, 1679 Entries:16/little, 1680 Size:32/little, 1681 Offset:32/little, 1682 ZipCommentLength:16/little, 1683 Comment:ZipCommentLength/binary>>) -> 1684 {#eocd{disk_num = DiskNum, 1685 start_disk_num = StartDiskNum, 1686 entries_on_disk = EntriesOnDisk, 1687 entries = Entries, 1688 size = Size, 1689 offset = Offset, 1690 zip_comment_length = ZipCommentLength}, 1691 Comment}; 1692eocd_and_comment_from_bin(_) -> 1693 throw(bad_eocd). 1694 1695cd_file_header_from_bin(<<VersionMadeBy:16/little, 1696 VersionNeeded:16/little, 1697 GPFlag:16/little, 1698 CompMethod:16/little, 1699 LastModTime:16/little, 1700 LastModDate:16/little, 1701 CRC32:32/little, 1702 CompSize:32/little, 1703 UncompSize:32/little, 1704 FileNameLength:16/little, 1705 ExtraFieldLength:16/little, 1706 FileCommentLength:16/little, 1707 DiskNumStart:16/little, 1708 InternalAttr:16/little, 1709 ExternalAttr:32/little, 1710 LocalHeaderOffset:32/little>>) -> 1711 #cd_file_header{version_made_by = VersionMadeBy, 1712 version_needed = VersionNeeded, 1713 gp_flag = GPFlag, 1714 comp_method = CompMethod, 1715 last_mod_time = LastModTime, 1716 last_mod_date = LastModDate, 1717 crc32 = CRC32, 1718 comp_size = CompSize, 1719 uncomp_size = UncompSize, 1720 file_name_length = FileNameLength, 1721 extra_field_length = ExtraFieldLength, 1722 file_comment_length = FileCommentLength, 1723 disk_num_start = DiskNumStart, 1724 internal_attr = InternalAttr, 1725 external_attr = ExternalAttr, 1726 local_header_offset = LocalHeaderOffset}; 1727cd_file_header_from_bin(_) -> 1728 throw(bad_cd_file_header). 1729 1730local_file_header_from_bin(<<VersionNeeded:16/little, 1731 GPFlag:16/little, 1732 CompMethod:16/little, 1733 LastModTime:16/little, 1734 LastModDate:16/little, 1735 CRC32:32/little, 1736 CompSize:32/little, 1737 UncompSize:32/little, 1738 FileNameLength:16/little, 1739 ExtraFieldLength:16/little>>) -> 1740 #local_file_header{version_needed = VersionNeeded, 1741 gp_flag = GPFlag, 1742 comp_method = CompMethod, 1743 last_mod_time = LastModTime, 1744 last_mod_date = LastModDate, 1745 crc32 = CRC32, 1746 comp_size = CompSize, 1747 uncomp_size = UncompSize, 1748 file_name_length = FileNameLength, 1749 extra_field_length = ExtraFieldLength}; 1750local_file_header_from_bin(_) -> 1751 throw(bad_local_file_header). 1752 1753%% make a file_info from a local directory header 1754%% local_file_header_to_file_info( 1755%% #local_file_header{last_mod_time = ModTime, 1756%% last_mod_date = ModDate, 1757%% uncomp_size = UncompSize}) -> 1758%% T = dos_date_time_to_datetime(ModDate, ModTime), 1759%% FI = #file_info{size = UncompSize, 1760%% type = regular, 1761%% access = read_write, 1762%% atime = T, 1763%% mtime = T, 1764%% ctime = T, 1765%% mode = 8#066, 1766%% links = 1, 1767%% major_device = 0, 1768%% minor_device = 0, 1769%% inode = 0, 1770%% uid = 0, 1771%% gid = 0}, 1772%% FI. 1773 1774%% io functions 1775binary_io({file_info, {_Filename, _B, #file_info{} = FI}}, _A) -> 1776 FI; 1777binary_io({file_info, {_Filename, #file_info{} = FI, _B}}, _A) -> 1778 FI; 1779binary_io({file_info, {_Filename, B}}, A) -> 1780 binary_io({file_info, B}, A); 1781binary_io({file_info, B}, _) -> 1782 {Type, Size} = 1783 if 1784 is_binary(B) -> {regular, byte_size(B)}; 1785 B =:= directory -> {directory, 0} 1786 end, 1787 Now = calendar:local_time(), 1788 #file_info{size = Size, type = Type, 1789 access = read_write, atime = Now, 1790 mtime = Now, ctime = Now, mode = 0, 1791 links = 1, major_device = 0, 1792 minor_device = 0, inode = 0, 1793 uid = 0, gid = 0}; 1794binary_io({open, {_Filename, B, _FI}, _Opts}, _) when is_binary(B) -> 1795 {0, B}; 1796binary_io({open, {_Filename, _FI, B}, _Opts}, _) when is_binary(B) -> 1797 {0, B}; 1798binary_io({open, {_Filename, B}, _Opts}, _) when is_binary(B) -> 1799 {0, B}; 1800binary_io({open, B, _Opts}, _) when is_binary(B) -> 1801 {0, B}; 1802binary_io({open, Filename, _Opts}, _) when is_list(Filename) -> 1803 {0, <<>>}; 1804binary_io({read, N}, {Pos, B}) when Pos >= byte_size(B) -> 1805 {eof, {Pos+N, B}}; 1806binary_io({read, N}, {Pos, B}) when Pos + N > byte_size(B) -> 1807 <<_:Pos/binary, Read/binary>> = B, 1808 {Read, {byte_size(B), B}}; 1809binary_io({pread, Pos, N}, {OldPos, B}) -> 1810 case B of 1811 <<_:Pos/binary, Read:N/binary, _Rest/binary>> -> 1812 {Read, {Pos+N, B}}; 1813 _ -> 1814 {eof, {OldPos, B}} 1815 end; 1816binary_io({read, N}, {Pos, B}) -> 1817 <<_:Pos/binary, Read:N/binary, _/binary>> = B, 1818 {Read, {Pos+N, B}}; 1819binary_io({seek, bof, Pos}, {_OldPos, B}) -> 1820 {Pos, B}; 1821binary_io({seek, cur, Pos}, {OldPos, B}) -> 1822 {OldPos + Pos, B}; 1823binary_io({seek, eof, Pos}, {_OldPos, B}) -> 1824 {byte_size(B) + Pos, B}; 1825binary_io({pwrite, Pos, Data}, {OldPos, B}) -> 1826 {OldPos, pwrite_binary(B, Pos, Data)}; 1827binary_io({write, Data}, {Pos, B}) -> 1828 {Pos + erlang:iolist_size(Data), pwrite_binary(B, Pos, Data)}; 1829binary_io(close, {_Pos, B}) -> 1830 B; 1831binary_io({close, FN}, {_Pos, B}) -> 1832 {FN, B}; 1833binary_io({list_dir, _F}, _B) -> 1834 []; 1835binary_io({set_file_info, _F, _FI}, B) -> 1836 B; 1837binary_io({ensure_dir, _Dir}, B) -> 1838 B. 1839 1840file_io({file_info, F}, _) -> 1841 case file:read_file_info(F) of 1842 {ok, Info} -> Info; 1843 {error, E} -> throw(E) 1844 end; 1845file_io({open, FN, Opts}, _) -> 1846 case lists:member(write, Opts) of 1847 true -> ok = filelib:ensure_dir(FN); 1848 _ -> ok 1849 end, 1850 case file:open(FN, Opts++[binary]) of 1851 {ok, H} -> H; 1852 {error, E} -> throw(E) 1853 end; 1854file_io({read, N}, H) -> 1855 case file:read(H, N) of 1856 {ok, B} -> {B, H}; 1857 eof -> {eof, H}; 1858 {error, E} -> throw(E) 1859 end; 1860file_io({pread, Pos, N}, H) -> 1861 case file:pread(H, Pos, N) of 1862 {ok, B} -> {B, H}; 1863 eof -> {eof, H}; 1864 {error, E} -> throw(E) 1865 end; 1866file_io({seek, S, Pos}, H) -> 1867 case file:position(H, {S, Pos}) of 1868 {ok, _NewPos} -> H; 1869 {error, Error} -> throw(Error) 1870 end; 1871file_io({write, Data}, H) -> 1872 case file:write(H, Data) of 1873 ok -> H; 1874 {error, Error} -> throw(Error) 1875 end; 1876file_io({pwrite, Pos, Data}, H) -> 1877 case file:pwrite(H, Pos, Data) of 1878 ok -> H; 1879 {error, Error} -> throw(Error) 1880 end; 1881file_io({close, FN}, H) -> 1882 case file:close(H) of 1883 ok -> FN; 1884 {error, Error} -> throw(Error) 1885 end; 1886file_io(close, H) -> 1887 file_io({close, ok}, H); 1888file_io({list_dir, F}, _H) -> 1889 case file:list_dir(F) of 1890 {ok, Files} -> Files; 1891 {error, Error} -> throw(Error) 1892 end; 1893file_io({set_file_info, F, FI}, H) -> 1894 case file:write_file_info(F, FI) of 1895 ok -> H; 1896 {error, Error} -> throw(Error) 1897 end; 1898file_io({ensure_dir, Dir}, H) -> 1899 ok = filelib:ensure_dir(Dir), 1900 H. 1901