1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2006-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: -module(zip_SUITE).
   20: 
   21: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   22: 	 init_per_group/2,end_per_group/2, borderline/1, atomic/1,
   23:          bad_zip/1, unzip_from_binary/1, unzip_to_binary/1,
   24:          zip_to_binary/1,
   25:          unzip_options/1, zip_options/1, list_dir_options/1, aliases/1,
   26:          openzip_api/1, zip_api/1, unzip_jar/1,
   27:          compress_control/1,
   28: 	 foldl/1]).
   29: 
   30: -include_lib("test_server/include/test_server.hrl").
   31: -include("test_server_line.hrl").
   32: -include_lib("kernel/include/file.hrl").
   33: -include_lib("stdlib/include/zip.hrl").
   34: 
   35: suite() -> [{ct_hooks,[ts_install_cth]}].
   36: 
   37: all() -> 
   38:     [borderline, atomic, bad_zip, unzip_from_binary,
   39:      unzip_to_binary, zip_to_binary, unzip_options,
   40:      zip_options, list_dir_options, aliases, openzip_api,
   41:      zip_api, unzip_jar, compress_control, foldl].
   42: 
   43: groups() -> 
   44:     [].
   45: 
   46: init_per_suite(Config) ->
   47:     Config.
   48: 
   49: end_per_suite(_Config) ->
   50:     ok.
   51: 
   52: init_per_group(_GroupName, Config) ->
   53:     Config.
   54: 
   55: end_per_group(_GroupName, Config) ->
   56:     Config.
   57: 
   58: 
   59: borderline(doc) ->
   60:     ["Test creating, listing and extracting one file from an archive "
   61:      "multiple times with different file sizes. Also check that the "
   62:      "modification date of the extracted file has survived."];
   63: borderline(Config) when is_list(Config) ->
   64:     RootDir = ?config(priv_dir, Config),
   65:     TempDir = filename:join(RootDir, "borderline"),
   66:     ok = file:make_dir(TempDir),
   67: 
   68:     Record = 512,
   69:     Block = 20 * Record,
   70: 
   71:     lists:foreach(fun(Size) -> borderline_test(Size, TempDir) end,
   72:                   [0, 1, 10, 13, 127, 333, Record-1, Record, Record+1,
   73:                    Block-Record-1, Block-Record, Block-Record+1,
   74:                    Block-1, Block, Block+1,
   75:                    Block+Record-1, Block+Record, Block+Record+1]),
   76: 
   77:     %% Clean up.
   78:     delete_files([TempDir]),
   79:     ok.
   80: 
   81: borderline_test(Size, TempDir) ->
   82:     Archive = filename:join(TempDir, "ar_"++integer_to_list(Size)++".zip"),
   83:     Name = filename:join(TempDir, "file_"++integer_to_list(Size)),
   84:     io:format("Testing size ~p", [Size]),
   85: 
   86:     %% Create a file and archive it.
   87:     {_, _, X0} = erlang:now(),
   88:     file:write_file(Name, random_byte_list(X0, Size)),
   89:     {ok, Archive} = zip:zip(Archive, [Name]),
   90:     ok = file:delete(Name),
   91: 
   92:     %% Verify listing and extracting.
   93:     {ok, [#zip_comment{comment = []},
   94:           #zip_file{name = Name,
   95:                     info = Info,
   96:                     offset = 0,
   97:                     comp_size = _}]} = zip:list_dir(Archive),
   98:     Size = Info#file_info.size,
   99:     {ok, [Name]} = zip:extract(Archive, [verbose]),
  100: 
  101:     %% Verify contents of extracted file.
  102:     {ok, Bin} = file:read_file(Name),
  103:     true = match_byte_list(X0, binary_to_list(Bin)),
  104: 
  105: 
  106:     %% Verify that Unix zip can read it. (if we have a unix zip that is!)
  107:     unzip_list(Archive, Name),
  108: 
  109:     ok.
  110: 
  111: unzip_list(Archive, Name) ->
  112:     case unix_unzip_exists() of
  113: 	true ->
  114:             unzip_list1(Archive, Name);
  115:         _ ->
  116:             ok
  117:     end.
  118: 
  119: %% Used to do os:find_executable() to check if unzip exists, but on
  120: %% some hosts that would give an unzip program which did not take the
  121: %% "-Z" option.
  122: %% Here we check that "unzip -Z" (which should display usage) and
  123: %% check that it exists with status 0.
  124: unix_unzip_exists() ->
  125:     case os:type() of
  126: 	{unix,_} ->
  127: 	    Port = open_port({spawn,"unzip -Z > /dev/null"}, [exit_status]),
  128: 	    receive
  129: 		{Port,{exit_status,0}} ->
  130: 		    true;
  131: 		{Port,{exit_status,_Fail}} ->
  132: 		    false
  133: 	    end;
  134: 	_ ->
  135: 	    false
  136:     end.
  137: 
  138: unzip_list1(Archive, Name) ->
  139:     Expect = Name ++ "\n",
  140:     cmd_expect("unzip -Z -1 " ++ Archive, Expect).
  141: 
  142: cmd_expect(Cmd, Expect) ->
  143:     Port = open_port({spawn, make_cmd(Cmd)}, [stream, in, eof]),
  144:     get_data(Port, Expect).
  145: 
  146: get_data(Port, Expect) ->
  147:     receive
  148:         {Port, {data, Bytes}} ->
  149:             get_data(Port, match_output(Bytes, Expect, Port));
  150:         {Port, eof} ->
  151:             Port ! {self(), close},
  152:             receive
  153:                 {Port, closed} ->
  154:                     true
  155:             end,
  156:             receive
  157:                 {'EXIT',  Port,  _} ->
  158:                     ok
  159:             after 1 ->                          % force context switch
  160:                     ok
  161:             end,
  162:             match_output(eof, Expect, Port)
  163:     end.
  164: 
  165: match_output([C|Output], [C|Expect], Port) ->
  166:     match_output(Output, Expect, Port);
  167: match_output([_|_], [_|_], Port) ->
  168:     kill_port_and_fail(Port, badmatch);
  169: match_output([X|Output], [], Port) ->
  170:     kill_port_and_fail(Port, {too_much_data, [X|Output]});
  171: match_output([], Expect, _Port) ->
  172:     Expect;
  173: match_output(eof, [], _Port) ->
  174:     [];
  175: match_output(eof, Expect, Port) ->
  176:     kill_port_and_fail(Port, {unexpected_end_of_input, Expect}).
  177: 
  178: kill_port_and_fail(Port, Reason) ->
  179:     unlink(Port),
  180:     exit(Port, die),
  181:     test_server:fail(Reason).
  182: 
  183: make_cmd(Cmd) ->
  184:     Cmd.
  185: %%     case os:type() of
  186: %%      {win32, _} -> lists:concat(["cmd /c",  Cmd]);
  187: %%      {unix, _}  -> lists:concat(["sh -c '",  Cmd,  "'"])
  188: %%     end.
  189: 
  190: %% Verifies a random byte list.
  191: 
  192: match_byte_list(X0, [Byte|Rest]) ->
  193:     X = next_random(X0),
  194:     case (X bsr 26) band 16#ff of
  195:         Byte -> match_byte_list(X, Rest);
  196:         _ -> false
  197:     end;
  198: match_byte_list(_, []) ->
  199:     true.
  200: 
  201: %% Generates a random byte list.
  202: 
  203: random_byte_list(X0, Count) ->
  204:     random_byte_list(X0, Count, []).
  205: 
  206: random_byte_list(X0, Count, Result) when Count > 0->
  207:     X = next_random(X0),
  208:     random_byte_list(X, Count-1, [(X bsr 26) band 16#ff|Result]);
  209: random_byte_list(_X, 0, Result) ->
  210:     lists:reverse(Result).
  211: 
  212: %% This RNG is from line 21 on page 102 in Knuth: The Art of Computer Programming,
  213: %% Volume II, Seminumerical Algorithms.
  214: 
  215: next_random(X) ->
  216:     (X*17059465+1) band 16#fffffffff.
  217: 
  218: atomic(doc) ->
  219:     ["Test the 'atomic' operations: zip/unzip/list_dir, on archives."
  220:      "Also test the 'cooked' option."];
  221: atomic(suite) -> [];
  222: atomic(Config) when is_list(Config) ->
  223:     ok = file:set_cwd(?config(priv_dir, Config)),
  224:     DataFiles = data_files(),
  225:     Names = [Name || {Name,_,_} <- DataFiles],
  226:     io:format("Names: ~p", [Names]),
  227: 
  228:     %% Create a zip  archive.
  229: 
  230:     Zip2 = "zip.zip",
  231:     {ok, Zip2} = zip:zip(Zip2, Names, []),
  232:     Names = names_from_list_dir(zip:list_dir(Zip2)),
  233: 
  234:     %% Same test again, but this time created with 'cooked'
  235: 
  236:     Zip3 = "cooked.zip",
  237:     {ok, Zip3} = zip:zip(Zip3, Names, [cooked]),
  238:     Names = names_from_list_dir(zip:list_dir(Zip3)),
  239:     Names = names_from_list_dir(zip:list_dir(Zip3, [cooked])),
  240: 
  241:     %% Clean up.
  242:     delete_files([Zip2,Zip3|Names]),
  243: 
  244:     ok.
  245: 
  246: openzip_api(doc) ->
  247:     ["Test the openzip_open/2, openzip_get/1, openzip_get/2, openzip_close/1 "
  248:      "and openzip_list_dir/1 functions."];
  249: openzip_api(suite) -> [];
  250: openzip_api(Config) when is_list(Config) ->
  251:     ok = file:set_cwd(?config(priv_dir, Config)),
  252:     DataFiles = data_files(),
  253:     Names = [Name || {Name, _, _} <- DataFiles],
  254:     io:format("Names: ~p", [Names]),
  255: 
  256:     %% Create a zip archive
  257: 
  258:     Zip = "zip.zip",
  259:     {ok, Zip} = zip:zip(Zip, Names, []),
  260: 
  261:     %% Open archive
  262:     {ok, OpenZip} = zip:openzip_open(Zip, [memory]),
  263: 
  264:     %% List dir
  265:     Names = names_from_list_dir(zip:openzip_list_dir(OpenZip)),
  266: 
  267:     %% Get a file
  268:     Name1 = hd(Names),
  269:     {ok, Data1} = file:read_file(Name1),
  270:     {ok, {Name1, Data1}} = zip:openzip_get(Name1, OpenZip),
  271: 
  272:     %% Get all files
  273:     FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(Name),
  274:                                         {Name, B} end, Names),
  275:     {ok, FilesDatas} = zip:openzip_get(OpenZip),
  276: 
  277:     %% Close
  278:     ok = zip:openzip_close(OpenZip),
  279: 
  280:     %% Clean up.
  281:     delete_files([Names]),
  282: 
  283:     ok.
  284: 
  285: zip_api(doc) ->
  286:     ["Test the zip_open/2, zip_get/1, zip_get/2, zip_close/1 "
  287:      "and zip_list_dir/1 functions."];
  288: zip_api(suite) -> [];
  289: zip_api(Config) when is_list(Config) ->
  290:     ok = file:set_cwd(?config(priv_dir, Config)),
  291:     DataFiles = data_files(),
  292:     Names = [Name || {Name, _, _} <- DataFiles],
  293:     io:format("Names: ~p", [Names]),
  294: 
  295:     %% Create a zip archive
  296:     Zip = "zip.zip",
  297:     {ok, Zip} = zip:zip(Zip, Names, []),
  298: 
  299:     %% Open archive
  300:     {ok, ZipSrv} = zip:zip_open(Zip, [memory]),
  301: 
  302:     %% List dir
  303:     Names = names_from_list_dir(zip:zip_list_dir(ZipSrv)),
  304: 
  305:     %% Get a file
  306:     Name1 = hd(Names),
  307:     {ok, Data1} = file:read_file(Name1),
  308:     {ok, {Name1, Data1}} = zip:zip_get(Name1, ZipSrv),
  309: 
  310:     %% Get all files
  311:     FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(Name),
  312:                                         {Name, B} end, Names),
  313:     {ok, FilesDatas} = zip:zip_get(ZipSrv),
  314: 
  315:     %% Close
  316:     ok = zip:zip_close(ZipSrv),
  317: 
  318:     %% Clean up.
  319:     delete_files([Names]),
  320: 
  321:     ok.
  322: 
  323: unzip_options(doc) ->
  324:     ["Test options for unzip, only cwd and file_list currently"];
  325: unzip_options(suite) ->
  326:     [];
  327: unzip_options(Config) when is_list(Config) ->
  328:     DataDir = ?config(data_dir, Config),
  329:     PrivDir = ?config(priv_dir, Config),
  330:     Long = filename:join(DataDir, "abc.zip"),
  331: 
  332:     %% create a temp directory
  333:     Subdir = filename:join(PrivDir, "t"),
  334:     ok = file:make_dir(Subdir),
  335: 
  336:     FList = ["quotes/rain.txt","wikipedia.txt"],
  337: 
  338:     %% Unzip a zip file in Subdir
  339:     ?line {ok, RetList} = zip:unzip(Long, [{cwd, Subdir},
  340:                                            {file_list, FList}]),
  341: 
  342:     %% Verify.
  343:     ?line true = (length(FList) =:= length(RetList)),
  344:     ?line lists:foreach(fun(F)-> {ok,B} = file:read_file(filename:join(DataDir, F)),
  345:                                  {ok,B} = file:read_file(filename:join(Subdir, F)) end,
  346:                         FList),
  347:     ?line lists:foreach(fun(F)-> ok = file:delete(F) end,
  348:                         RetList),
  349: 
  350:     %% Clean up and verify no more files.
  351:     ?line 0 = delete_files([Subdir]),
  352:     ok.
  353: 
  354: unzip_jar(doc) ->
  355:     ["Test unzip a jar file (OTP-7382)"];
  356: unzip_jar(suite) ->
  357:     [];
  358: unzip_jar(Config) when is_list(Config) ->
  359:     DataDir = ?config(data_dir, Config),
  360:     PrivDir = ?config(priv_dir, Config),
  361:     JarFile = filename:join(DataDir, "test.jar"),
  362: 
  363:     %% create a temp directory
  364:     Subdir = filename:join(PrivDir, "jartest"),
  365:     ok = file:make_dir(Subdir),
  366:     ok = file:set_cwd(Subdir),
  367: 
  368:     FList = ["META-INF/MANIFEST.MF","test.txt"],
  369: 
  370:     {ok, RetList} = zip:unzip(JarFile),
  371: 
  372:     %% Verify.
  373:     ?line lists:foreach(fun(F)-> {ok,B} = file:read_file(filename:join(DataDir, F)),
  374:                                  {ok,B} = file:read_file(filename:join(Subdir, F)) end,
  375:                         FList),
  376:     ?line lists:foreach(fun(F)-> ok = file:delete(F) end,
  377:                         RetList),
  378: 
  379:     %% Clean up and verify no more files.
  380:     ?line 0 = delete_files([Subdir]),
  381:     ok.
  382: 
  383: zip_options(doc) ->
  384:     ["Test the options for unzip, only cwd currently"];
  385: zip_options(suite) ->
  386:     [];
  387: zip_options(Config) when is_list(Config) ->
  388:     PrivDir = ?config(priv_dir, Config),
  389:     ok = file:set_cwd(PrivDir),
  390:     DataFiles = data_files(),
  391:     Names = [Name || {Name, _, _} <- DataFiles],
  392: 
  393:     %% Make sure cwd is not where we get the files
  394:     ok = file:set_cwd(?config(data_dir, Config)),
  395: 
  396:     %% Create a zip archive
  397:     {ok, {_,Zip}} =
  398:         zip:zip("filename_not_used.zip", Names, [memory, {cwd, PrivDir}]),
  399: 
  400:     %% Open archive
  401:     {ok, ZipSrv} = zip:zip_open(Zip, [memory]),
  402: 
  403:     %% List dir
  404:     Names = names_from_list_dir(zip:zip_list_dir(ZipSrv)),
  405: 
  406:     %% Get a file
  407:     Name1 = hd(Names),
  408:     {ok, Data1} = file:read_file(filename:join(PrivDir, Name1)),
  409:     {ok, {Name1, Data1}} = zip:zip_get(Name1, ZipSrv),
  410: 
  411:     %% Get all files
  412:     FilesDatas = lists:map(fun(Name) -> {ok, B} = file:read_file(filename:join(PrivDir, Name)),
  413:                                         {Name, B} end, Names),
  414:     {ok, FilesDatas} = zip:zip_get(ZipSrv),
  415: 
  416:     %% Close
  417:     ok = zip:zip_close(ZipSrv),
  418: 
  419:     %% Clean up.
  420:     delete_files([Names]),
  421: 
  422:     ok.
  423: 
  424: list_dir_options(doc) ->
  425:     ["Test the options for list_dir... one day"];
  426: list_dir_options(suite) ->
  427:     [];
  428: list_dir_options(Config) when is_list(Config) ->
  429:     ok.
  430: 
  431: 
  432: 
  433: 
  434: %% convert zip_info as returned from list_dir to a list of names
  435: names_from_list_dir({ok, Info}) ->
  436:     names_from_list_dir(Info);
  437: names_from_list_dir(Info) ->
  438:     tl(lists:map(fun(#zip_file{name = Name}) -> Name;
  439:                     (_) -> ok end, Info)).
  440: 
  441: %% Returns a sequence of characters.
  442: char_seq(N, First) ->
  443:     char_seq(N, First, []).
  444: 
  445: char_seq(0, _, Result) ->
  446:     Result;
  447: char_seq(N, C, Result) when C < 127 ->
  448:     char_seq(N-1, C+1, [C|Result]);
  449: char_seq(N, _, Result) ->
  450:     char_seq(N, $!, Result).
  451: 
  452: data_files() ->
  453:     Files = [{"first_file", 1555, $a},
  454:              {"small_file", 7, $d},
  455:              {"big_file", 23875, $e},
  456:              {"last_file", 7500, $g}],
  457:     create_files(Files),
  458:     Files.
  459: 
  460: create_files([{Name, dir, _First}|Rest]) ->
  461:     ok = file:make_dir(Name),
  462:     create_files(Rest);
  463: create_files([{Name, Size, First}|Rest]) when is_integer(Size) ->
  464:     ok = file:write_file(Name, char_seq(Size, First)),
  465:     create_files(Rest);
  466: create_files([]) ->
  467:     ok.
  468: 
  469: %% make_dirs([Dir|Rest], []) ->
  470: %%     ok = file:make_dir(Dir),
  471: %%     make_dirs(Rest, Dir);
  472: %% make_dirs([Dir|Rest], Parent) ->
  473: %%     Name = filename:join(Parent, Dir),
  474: %%     ok = file:make_dir(Name),
  475: %%     make_dirs(Rest, Name);
  476: %% make_dirs([], Dir) ->
  477: %%     Dir.
  478: 
  479: bad_zip(doc) ->
  480:     ["Try zip:unzip/1 on some corrupted zip files."];
  481: bad_zip(Config) when is_list(Config) ->
  482:     ok = file:set_cwd(?config(priv_dir, Config)),
  483:     try_bad("bad_crc",    {bad_crc, "abc.txt"}, Config),
  484:     try_bad("bad_central_directory", bad_central_directory, Config),
  485:     try_bad("bad_file_header",    bad_file_header, Config),
  486:     try_bad("bad_eocd",    bad_eocd, Config),
  487:     try_bad("enoent", enoent, Config),
  488:     GetNotFound = fun(A) ->
  489:                           {ok, O} = zip:openzip_open(A, []),
  490:                           zip:openzip_get("not_here", O)
  491:                   end,
  492:     try_bad("abc", file_not_found, GetNotFound, Config),
  493:     ok.
  494: 
  495: try_bad(N, R, Config) ->
  496:     try_bad(N, R, fun(A) -> io:format("name : ~p\n", [A]),
  497:                             zip:unzip(A, [verbose]) end, Config).
  498: 
  499: try_bad(Name0, Reason, What, Config) ->
  500:     %% Intentionally no macros here.
  501: 
  502:     DataDir = ?config(data_dir, Config),
  503:     Name = Name0 ++ ".zip",
  504:     io:format("~nTrying ~s", [Name]),
  505:     Full = filename:join(DataDir, Name),
  506:     Expected = {error, Reason},
  507:     case What(Full) of
  508:         Expected ->
  509:             io:format("Result: ~p\n", [Expected]);
  510:         Other ->
  511:             io:format("unzip/2 returned ~p (expected ~p)\n", [Other, Expected]),
  512:             test_server:fail({bad_return_value, Other})
  513:     end.
  514: 
  515: unzip_to_binary(doc) ->
  516:     ["Test extracting to binary with memory option."];
  517: unzip_to_binary(Config) when is_list(Config) ->
  518:     DataDir = ?config(data_dir, Config),
  519:     PrivDir = ?config(priv_dir, Config),
  520: 
  521:     delete_all_in(PrivDir),
  522:     file:set_cwd(PrivDir),
  523:     Long = filename:join(DataDir, "abc.zip"),
  524: 
  525:     %% Unzip a zip file into a binary
  526:     {ok, FBList} = zip:unzip(Long, [memory]),
  527: 
  528:     %% Verify.
  529:     lists:foreach(fun({F,B}) -> {ok,B}=file:read_file(filename:join(DataDir, F))
  530:                   end, FBList),
  531: 
  532:     %% Make sure no files created in cwd
  533:     {ok,[]} = file:list_dir(PrivDir),
  534: 
  535:     ok.
  536: 
  537: zip_to_binary(doc) ->
  538:     ["Test compressing to binary with memory option."];
  539: zip_to_binary(Config) when is_list(Config) ->
  540:     DataDir = ?config(data_dir, Config),
  541:     PrivDir = ?config(priv_dir, Config),
  542:     delete_all_in(PrivDir),
  543:     file:set_cwd(PrivDir),
  544:     FileName = "abc.txt",
  545:     ZipName = "t.zip",
  546:     FilePath = filename:join(DataDir, FileName),
  547:     {ok, _Size} = file:copy(FilePath, FileName),
  548: 
  549:     %% Zip to a binary archive
  550:     {ok, {ZipName, ZipB}} = zip:zip(ZipName, [FileName], [memory]),
  551: 
  552:     %% Make sure no files created in cwd
  553:     {ok,[FileName]} = file:list_dir(PrivDir),
  554: 
  555:     %% Zip to a file
  556:     {ok, ZipName} = zip:zip(ZipName, [FileName]),
  557: 
  558:     %% Verify.
  559:     {ok, ZipB} = file:read_file(ZipName),
  560:     {ok, FData} = file:read_file(FileName),
  561:     {ok, [{FileName, FData}]} = zip:unzip(ZipB, [memory]),
  562: 
  563:     %% Clean up.
  564:     delete_files([FileName, ZipName]),
  565: 
  566:     ok.
  567: 
  568: aliases(doc) ->
  569:     ["Test using the aliases, extract/2, table/2 and create/3"];
  570: aliases(Config) when is_list(Config) ->
  571:     {_, _, X0} = erlang:now(),
  572:     Size = 100,
  573:     B = list_to_binary(random_byte_list(X0, Size)),
  574:     %% create
  575:     {ok, {"z.zip", ZArchive}} = zip:create("z.zip", [{"b", B}], [memory]),
  576:     %% extract
  577:     {ok, [{"b", B}]} = zip:extract(ZArchive, [memory]),
  578:     %% table
  579:     {ok, [#zip_comment{comment = _}, #zip_file{name = "b",
  580:                                                info = FI,
  581:                                                comp_size = _,
  582:                                                offset = 0}]} =
  583:         zip:table(ZArchive),
  584:     Size = FI#file_info.size,
  585: 
  586:     ok.
  587: 
  588: 
  589: 
  590: unzip_from_binary(doc) ->
  591:     ["Test extracting a zip archive from a binary."];
  592: unzip_from_binary(Config) when is_list(Config) ->
  593:     DataDir = ?config(data_dir, Config),
  594:     PrivDir = ?config(priv_dir, Config),
  595:     ExtractDir = filename:join(PrivDir, "extract_from_binary"),
  596:     ok = file:make_dir(ExtractDir),
  597:     Archive = filename:join(ExtractDir, "abc.zip"),
  598:     {ok, _Size} = file:copy(filename:join(DataDir, "abc.zip"), Archive),
  599:     FileName = "abc.txt",
  600:     Quote = "quotes/rain.txt",
  601:     Wikipedia = "wikipedia.txt",
  602:     EmptyFile = "emptyFile",
  603:     file:set_cwd(ExtractDir),
  604: 
  605:     %% Read a zip file into a binary and extract from the binary.
  606:     {ok, Bin} = file:read_file(Archive),
  607:     {ok, [FileName,Quote,Wikipedia,EmptyFile]} = zip:unzip(Bin),
  608: 
  609:     %% Verify.
  610:     DestFilename = filename:join(ExtractDir, "abc.txt"),
  611:     {ok, Data} = file:read_file(filename:join(DataDir, FileName)),
  612:     {ok, Data} = file:read_file(DestFilename),
  613: 
  614:     DestQuote = filename:join([ExtractDir, "quotes", "rain.txt"]),
  615:     {ok, QuoteData} = file:read_file(filename:join(DataDir, Quote)),
  616:     {ok, QuoteData} = file:read_file(DestQuote),
  617: 
  618:     %% Clean up.
  619:     delete_files([DestFilename, DestQuote, Archive, ExtractDir]),
  620:     ok.
  621: 
  622: %% oac_files() ->
  623: %%     Files = [{"oac_file", 1459, $x},
  624: %%           {"oac_small", 99, $w},
  625: %%           {"oac_big", 33896, $A}],
  626: %%     create_files(Files),
  627: %%     Files.
  628: 
  629: %% Delete the given list of files and directories.
  630: %% Return total number of deleted files (not directories)
  631: delete_files(List) ->
  632:     do_delete_files(List, 0).
  633: do_delete_files([],Cnt) ->
  634:     Cnt;
  635: do_delete_files([Item|Rest], Cnt) ->
  636:     case file:delete(Item) of
  637:         ok ->
  638:             DelCnt = 1;
  639:         {error,eperm} ->
  640:             file:change_mode(Item, 8#777),
  641:             DelCnt = delete_files(filelib:wildcard(filename:join(Item, "*"))),
  642:             file:del_dir(Item);
  643:         {error,eacces} ->
  644:             %% We'll see about that!
  645:             file:change_mode(Item, 8#777),
  646:             case file:delete(Item) of
  647:                 ok ->
  648: 		    DelCnt = 1;
  649:                 {error,_} ->
  650:                     erlang:yield(),
  651:                     file:change_mode(Item, 8#777),
  652:                     file:delete(Item),
  653:                     DelCnt = 1
  654:             end;
  655:         {error,_} ->
  656:             DelCnt = 0
  657:     end,
  658:     do_delete_files(Rest, Cnt + DelCnt).
  659: 
  660: delete_all_in(Dir) ->
  661:     {ok, Files} = file:list_dir(Dir),
  662:     delete_files(lists:map(fun(F) -> filename:join(Dir,F) end,
  663:                            Files)).
  664: 
  665: compress_control(doc) ->
  666:     ["Test control of which files that should be compressed"];
  667: compress_control(suite) -> [];
  668: compress_control(Config) when is_list(Config) ->
  669:     ok = file:set_cwd(?config(priv_dir, Config)),
  670:     Dir = "compress_control",
  671:     Files = [
  672:              {Dir,                                                          dir,   $d},
  673:              {filename:join([Dir, "first_file.txt"]), 10000, $f},
  674:              {filename:join([Dir, "a_dir"]), dir,   $d},
  675:              {filename:join([Dir, "a_dir", "zzz.zip"]), 10000, $z},
  676:              {filename:join([Dir, "a_dir", "lll.lzh"]), 10000, $l},
  677:              {filename:join([Dir, "a_dir", "eee.exe"]), 10000, $e},
  678:              {filename:join([Dir, "a_dir", "ggg.arj"]), 10000, $g},
  679:              {filename:join([Dir, "a_dir", "b_dir"]), dir,   $d},
  680:              {filename:join([Dir, "a_dir", "b_dir", "ggg.arj"]), 10000, $a},
  681:              {filename:join([Dir, "last_file.txt"]), 10000, $l}
  682:             ],
  683: 
  684:     test_compress_control(Dir,
  685: 			  Files,
  686: 			  [{compress, []}],
  687: 			  []),
  688: 
  689:     test_compress_control(Dir,
  690: 			  Files,
  691: 			  [{uncompress, all}],
  692: 			  []),
  693: 
  694:     test_compress_control(Dir,
  695: 			  Files,
  696: 			  [{uncompress, []}],
  697: 			  [".txt", ".exe", ".zip", ".lzh", ".arj"]),
  698: 
  699:     test_compress_control(Dir,
  700: 			  Files,
  701: 			  [],
  702: 			  [".txt", ".exe"]),
  703: 
  704:     test_compress_control(Dir,
  705: 			  Files,
  706: 			  [{uncompress, {add, [".exe"]}},
  707: 			   {uncompress, {del, [".zip", "arj"]}}],
  708: 			  [".txt", ".zip", "arj"]),
  709: 
  710:     test_compress_control(Dir,
  711: 			  Files,
  712: 			  [{uncompress, []},
  713: 			   {uncompress, {add, [".exe"]}},
  714: 			   {uncompress, {del, [".zip", "arj"]}}],
  715: 			  [".txt", ".zip", ".lzh", ".arj"]),
  716: 
  717:     ok.
  718: 
  719: test_compress_control(Dir, Files, ZipOptions, Expected) ->
  720:     %% Cleanup
  721:     Zip = "zip.zip",
  722:     Names = [N || {N, _, _} <- Files],
  723:     delete_files([Zip]),
  724:     delete_files(lists:reverse(Names)),
  725: 
  726:     create_files(Files),
  727:     {ok, Zip} = zip:create(Zip, [Dir], ZipOptions),
  728: 
  729:     {ok, OpenZip} = zip:openzip_open(Zip, [memory]),
  730:     {ok,[#zip_comment{comment = ""} | ZipList]} = zip:openzip_list_dir(OpenZip),
  731:     io:format("compress_control:  -> ~p  -> ~p\n  -> ~pn", [Expected, ZipOptions, ZipList]),
  732:     verify_compression(Files, ZipList, OpenZip, ZipOptions, Expected),
  733:     ok = zip:openzip_close(OpenZip),
  734: 
  735:     %% Cleanup
  736:     delete_files([Zip]),
  737:     delete_files(lists:reverse(Names)), % Remove plain files before directories
  738: 
  739:     ok.
  740: 
  741: verify_compression([{Name, Kind, _Filler} | Files], ZipList, OpenZip, ZipOptions, Expected) ->
  742:     {Name2, BinSz} =
  743:         case Kind of
  744:             dir ->
  745:                 {Name ++ "/", 0};
  746:             _   ->
  747:                 {ok, {Name, Bin}} = zip:openzip_get(Name, OpenZip),
  748:                 {Name, size(Bin)}
  749:         end,
  750:     {Name2, {value, ZipFile}} = {Name2, lists:keysearch(Name2,  #zip_file.name, ZipList)},
  751:     #zip_file{info = #file_info{size = InfoSz, type = InfoType}, comp_size = InfoCompSz} = ZipFile,
  752: 
  753:     Ext = filename:extension(Name),
  754:     IsComp = is_compressed(Ext, Kind, ZipOptions),
  755:     ExpComp = lists:member(Ext, Expected),
  756:     case {Name, Kind, InfoType, IsComp, ExpComp, BinSz, InfoSz, InfoCompSz} of
  757:         {_, dir, directory, false, _,     Sz, Sz, Sz}      when Sz =:= BinSz -> ok;
  758:         {_, Sz,  regular,   false, false, Sz, Sz, Sz}      when Sz =:= BinSz -> ok;
  759:         {_, Sz,  regular,   true,  true,  Sz, Sz, OtherSz} when Sz =:= BinSz, OtherSz =/= BinSz -> ok
  760:     end,
  761:     verify_compression(Files, ZipList -- [ZipFile], OpenZip, ZipOptions, Expected);
  762: verify_compression([], [], _OpenZip, _ZipOptions, _Expected) ->
  763:     ok.
  764: 
  765: is_compressed(_Ext, dir, _Options) ->
  766:     false;
  767: is_compressed(Ext, _Sz, Options) ->
  768:     CompressOpt =
  769:         case [What || {compress, What} <- Options] of
  770:             [] -> all;
  771:             CompressOpts-> extensions(CompressOpts, all)
  772:         end,
  773:     DoCompress = (CompressOpt =:= all) orelse lists:member(Ext, CompressOpt),
  774:     Default = [".Z", ".zip", ".zoo", ".arc", ".lzh", ".arj"],
  775:     UncompressOpt =
  776:         case [What || {uncompress, What} <- Options] of
  777:             [] -> Default;
  778:             UncompressOpts-> extensions(UncompressOpts, Default)
  779:         end,
  780:     DoUncompress = (UncompressOpt =:= all) orelse lists:member(Ext, UncompressOpt),
  781:     DoCompress andalso not DoUncompress.
  782: 
  783: extensions([H | T], Old) ->
  784:     case H of
  785:         all ->
  786:             extensions(T, H);
  787:         H when is_list(H) ->
  788:             extensions(T, H);
  789:         {add, New} when is_list(New), is_list(Old) ->
  790:             extensions(T, Old ++ New);
  791:         {del, New} when is_list(New), is_list(Old) ->
  792:             extensions(T, Old -- New);
  793:         _ ->
  794:             extensions(T, Old)
  795:     end;
  796: extensions([], Old) ->
  797:     Old.
  798: 
  799: foldl(Config) ->
  800:     PrivDir = ?config(priv_dir, Config),
  801:     File = filename:join([PrivDir, "foldl.zip"]),
  802: 
  803:     FooBin = <<"FOO">>,
  804:     BarBin = <<"BAR">>,
  805:     Files = [{"foo", FooBin}, {"bar", BarBin}],
  806:     ?line {ok, {File, Bin}} = zip:create(File, Files, [memory]),
  807:     ZipFun = fun(N, I, B, Acc) -> [{N, B(), I()} | Acc] end,
  808:     ?line {ok, FileSpec} = zip:foldl(ZipFun, [], {File, Bin}),
  809:     ?line [{"bar", BarBin, #file_info{}}, {"foo", FooBin, #file_info{}}] = FileSpec,
  810:     ?line {ok, {File, Bin}} = zip:create(File, lists:reverse(FileSpec), [memory]),
  811:     ?line {foo_bin, FooBin} =
  812: 	try
  813: 	    zip:foldl(fun("foo", _, B, _) -> throw(B()); (_, _, _, Acc) -> Acc end, [], {File, Bin})
  814: 	catch
  815: 	    throw:FooBin ->
  816: 		{foo_bin, FooBin}
  817: 	end,
  818:     ?line ok = file:write_file(File, Bin),
  819:     ?line {ok, FileSpec} = zip:foldl(ZipFun, [], File),
  820: 
  821:     ?line {error, einval} = zip:foldl(fun() -> ok end, [], File),
  822:     ?line {error, einval} = zip:foldl(ZipFun, [], 42),
  823:     ?line {error, einval} = zip:foldl(ZipFun, [], {File, 42}),
  824: 
  825:     ?line ok = file:delete(File),
  826:     ?line {error, enoent} = zip:foldl(ZipFun, [], File),
  827: 
  828:     ok.