%%% %%% lfm.erl %%% Copyright (C) 2009 James Lee %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License %%% as published by the Free Software Foundation; either version 2 %%% of the License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% ERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %%% GNU General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA %%% 02110-1301, USA. %%% %%% %%% Save streams from the last.fm flash player: %%% %%% 1. Add "127.0.0.1 play.last.fm" to /etc/hosts %%% 2. Start the proxy with: Listen = lfm:start(). %%% 3. Click play on the flash player. %%% 4. As soon as the download starts close the player. %%% 5. When the download finishes, stop the proxy with: lfm:stop(Listen). %%% -module(lfm). -author('James Lee '). -export([start/0, stop/1]). -export([accept_loop/2]). -include_lib("kernel/src/inet_dns.hrl"). start() -> ok = inet_db:add_ns({4,2,2,1}), % add a dns server to be used later {ok, Listen} = gen_tcp:listen(80, [binary, {packet, line}, {active, false}]), spawn(?MODULE, accept_loop, [Listen, 0]), Listen. stop(Listen) -> gen_tcp:close(Listen). accept_loop(Listen, Num) -> case gen_tcp:accept(Listen) of {ok, Socket} -> spawn(?MODULE, accept_loop, [Listen, Num + 1]), loop(Socket, "", [], Num); {error, closed} -> ok end. loop(Socket, Host, Lines, Num) -> case gen_tcp:recv(Socket, 0) of {ok, Line = <<"\r\n">>} -> % we've reached the end of the http headers gen_tcp:close(Socket), relay(Host, lists:reverse([Line|Lines]), Num); {ok, Line = <<"Host: ", HostBin/binary>>} -> % note down the host from the headers to relay to loop(Socket, binary_to_list(HostBin) -- "\r\n", [Line|Lines], Num); {ok, Line} -> loop(Socket, Host, [Line|Lines], Num) end. relay(Host, Lines, Num) -> % open a connection to the specified host based on a dns lookup % /etc/hosts is modified to for this proxy to work so we must look up % the real ip in dns to avoid an infinite loop {ok, Socket} = gen_tcp:connect(lookupa(Host), 80, [binary, {packet, line}, {active, false}]), gen_tcp:send(Socket, Lines), receive_response(Socket, Lines, Num). receive_response(Socket, Lines, Num) -> case gen_tcp:recv(Socket, 0) of {ok, <<"\r\n">>} -> % we've reached the end of the http response... % so open a file to save the content in Filename = "/tmp/" ++ integer_to_list(Num) ++ ".mp3", {ok, File} = file:open(Filename, write), io:format("Receiving ~p...~n", [Filename]), receive_mp3(Socket, File); {ok, <<"Location: ", LocationBin/binary>>} -> % when we get a redirect... % replace the old headers with the info from the redirect location {match, [HostBin, FileBin]} = re:run(LocationBin, "^http://([^/]*)(/.*)\r\n$", [{capture, all_but_first, binary}]), NewLines = [<<"GET ", FileBin/binary, " HTTP/1.1\r\n">>, <<"Host: ", HostBin/binary, "\r\n">> | lists:nthtail(2, Lines)], % and start a new http session gen_tcp:close(Socket), relay(binary_to_list(HostBin), NewLines, Num); {ok, _Other} -> receive_response(Socket, Lines, Num); {error, closed} -> ok end. receive_mp3(Socket, File) -> case gen_tcp:recv(Socket, 0) of {ok, Line} -> file:write(File, Line), receive_mp3(Socket, File); {error, closed} -> file:close(File), io:format("Finished receiving.~n") end. %% %% DNS lookup code from %% http://technicalmusings.blogspot.com/2007/08/erlang-to-stress-test-dns.html %% lookupa(Domain) -> {ok, #dns_rec{anlist = Ans}} = inet_res:nslookup(Domain, 1, a), finda(Ans). finda([#dns_rr{type=?S_A, data = Addr} | _]) -> Addr; finda([_ | T]) -> finda(T); finda(_Other) -> none.