Handle HAVE_ALL and HAVE_NONE. Cleanup the BITFIELD message.
[etorrent.git] / lib / etorrent-1.0 / src / etorrent_metainfo.erl
blob463341de325fbe95fab24562631bff574a89bced
1 %%%-------------------------------------------------------------------
2 %%% File : metainfo.erl
3 %%% Author : Jesper Louis Andersen <jlouis@succubus>
4 %%% License : See COPYING
5 %%% Description : Code for manipulating the metainfo file
6 %%%
7 %%% Created : 24 Jan 2007 by Jesper Louis Andersen <jlouis@succubus>
8 %%%-------------------------------------------------------------------
10 -module(etorrent_metainfo).
11 -author("Jesper Louis Andersen <jesper.louis.andersen@gmail.com>").
12 -vsn(1).
14 %% API
15 -export([get_piece_length/1, get_length/1, get_pieces/1, get_url/1,
16 get_infohash/1,
17 get_files/1, get_name/1, hexify/1]).
20 %%====================================================================
21 %% API
22 %%====================================================================
24 %%--------------------------------------------------------------------
25 %% Function: get_piece_length/1
26 %% Description: Search a torrent file, return the piece length
27 %%--------------------------------------------------------------------
28 get_piece_length(Torrent) ->
29 {integer, Size} =
30 etorrent_bcoding:search_dict({string, "piece length"},
31 get_info(Torrent)),
32 Size.
34 %%--------------------------------------------------------------------
35 %% Function: get_pieces/1
36 %% Description: Search a torrent, return pieces as a list
37 %%--------------------------------------------------------------------
38 get_pieces(Torrent) ->
39 {string, Ps} = etorrent_bcoding:search_dict({string, "pieces"},
40 get_info(Torrent)),
41 [list_to_binary(S) || S <- split_into_chunks(20, Ps)].
43 get_length(Torrent) ->
44 case etorrent_bcoding:search_dict({string, "length"},
45 get_info(Torrent)) of
46 {integer, L} ->
48 false ->
49 %% Multifile torrent
50 sum_files(Torrent)
51 end.
53 get_file_length(File) ->
54 {integer, N} = etorrent_bcoding:search_dict({string, "length"},
55 File),
58 sum_files(Torrent) ->
59 {list, Files} = etorrent_bcoding:search_dict({string, "files"},
60 get_info(Torrent)),
61 lists:sum([get_file_length(F) || F <- Files]).
63 %%--------------------------------------------------------------------
64 %% Function: get_files/1
65 %% Description: Get a file list from the torrent
66 %%--------------------------------------------------------------------
67 get_files(Torrent) ->
68 {list, FilesEntries} = get_files_section(Torrent),
69 [process_file_entry(Path) || Path <- FilesEntries].
71 %%--------------------------------------------------------------------
72 %% Function: get_name/1
73 %% Description: Get the name of a torrent. Returns either {ok, N} for
74 %% for a valid name or {error, security_violation, N} for something
75 %% that violates the security limitations.
76 %%--------------------------------------------------------------------
77 get_name(Torrent) ->
78 {string, N} = etorrent_bcoding:search_dict({string, "name"},
79 get_info(Torrent)),
80 true = valid_path(N),
83 %%--------------------------------------------------------------------
84 %% Function: get_url/1
85 %% Description: Return the URL of a torrent
86 %%--------------------------------------------------------------------
87 get_url(Torrent) ->
88 {string, U} = etorrent_bcoding:search_dict({string, "announce"},
89 Torrent),
92 %%--------------------------------------------------------------------
93 %% Function: get_infohash/1
94 %% Description: Return the infohash for a torrent
95 %%--------------------------------------------------------------------
96 get_infohash(Torrent) ->
97 InfoDict = etorrent_bcoding:search_dict({string, "info"}, Torrent),
98 InfoString = etorrent_bcoding:encode(InfoDict),
99 crypto:sha(list_to_binary(InfoString)).
102 %%====================================================================
103 %% Internal functions
104 %%====================================================================
105 get_info(Torrent) ->
106 etorrent_bcoding:search_dict({string, "info"}, Torrent).
108 split_into_chunks(_N, []) ->
110 split_into_chunks(N, String) ->
111 {Chunk, Rest} = lists:split(N, String),
112 [Chunk | split_into_chunks(N, Rest)].
114 hexify(Digest) ->
115 lists:concat(
116 [lists:concat(io_lib:format("~.16B", [C]))
117 || C <- binary_to_list(Digest)]).
119 %%--------------------------------------------------------------------
120 %% Function: valid_path(Path)
121 %% Description: Predicate that tests the torrent only contains paths
122 %% which are not a security threat. Stolen from Bram Cohen's original
123 %% client.
124 %%--------------------------------------------------------------------
125 valid_path(Path) ->
126 RE = "^[^/\\.~][^\\/]*$",
127 case regexp:match(Path, RE) of
128 {match, _S, _E} ->
129 true;
130 nomatch ->
131 false
132 end.
134 process_file_entry(Entry) ->
135 {dict, Dict} = Entry,
136 {value, {{string, "path"},
137 {list, Path}}} =
138 lists:keysearch({string, "path"}, 1, Dict),
139 {value, {{string, "length"},
140 {integer, Size}}} =
141 lists:keysearch({string, "length"}, 1, Dict),
142 true = lists:any(fun({string, P}) -> valid_path(P) end, Path),
143 Filename = filename:join([X || {string, X} <- Path]),
144 {Filename, Size}.
146 get_files_section(Torrent) ->
147 case etorrent_bcoding:search_dict({string, "files"}, get_info(Torrent)) of
148 false ->
149 % Single value torrent, fake entry
150 N = get_name(Torrent),
151 L = get_length(Torrent),
152 {list,[{dict,[{{string,"path"},
153 {list,[{string,N}]}},
154 {{string,"length"},{integer,L}}]}]};
155 V -> V
156 end.