1 %%%-------------------------------------------------------------------
2 %%% File : metainfo.erl
3 %%% Author : Jesper Louis Andersen <jlouis@succubus>
4 %%% License : See COPYING
5 %%% Description : Code for manipulating the metainfo file
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>").
15 -export([get_piece_length
/1, get_length
/1, get_pieces
/1, get_url
/1,
17 get_files
/1, get_name
/1, hexify
/1]).
20 %%====================================================================
22 %%====================================================================
24 %%--------------------------------------------------------------------
25 %% Function: get_piece_length/1
26 %% Description: Search a torrent file, return the piece length
27 %%--------------------------------------------------------------------
28 get_piece_length(Torrent
) ->
30 etorrent_bcoding:search_dict({string
, "piece length"},
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"},
41 [list_to_binary(S
) || S
<- split_into_chunks(20, Ps
)].
43 get_length(Torrent
) ->
44 case etorrent_bcoding:search_dict({string
, "length"},
53 get_file_length(File
) ->
54 {integer, N
} = etorrent_bcoding:search_dict({string
, "length"},
59 {list, Files
} = etorrent_bcoding:search_dict({string
, "files"},
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 %%--------------------------------------------------------------------
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 %%--------------------------------------------------------------------
78 {string
, N
} = etorrent_bcoding:search_dict({string
, "name"},
83 %%--------------------------------------------------------------------
84 %% Function: get_url/1
85 %% Description: Return the URL of a torrent
86 %%--------------------------------------------------------------------
88 {string
, U
} = etorrent_bcoding:search_dict({string
, "announce"},
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 %%====================================================================
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
)].
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
124 %%--------------------------------------------------------------------
126 RE
= "^[^/\\.~][^\\/]*$",
127 case regexp:match(Path
, RE
) of
134 process_file_entry(Entry
) ->
135 {dict
, Dict
} = Entry
,
136 {value
, {{string
, "path"},
138 lists:keysearch({string
, "path"}, 1, Dict
),
139 {value
, {{string
, "length"},
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
]),
146 get_files_section(Torrent
) ->
147 case etorrent_bcoding:search_dict({string
, "files"}, get_info(Torrent
)) of
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
}}]}]};