aboutsummaryrefslogtreecommitdiffstats
path: root/examples/rest_pastebin/src/toppage_handler.erl
blob: 80974feb246810eb029c21993b13c8120d95ee96 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
%% Feel free to use, reuse and abuse the code in this file.

%% @doc Pastebin handler.
-module(toppage_handler).

%% Standard callbacks.
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_provided/2]).
-export([content_types_accepted/2]).
-export([resource_exists/2]).

%% Custom callbacks.
-export([create_paste/2]).
-export([paste_html/2]).
-export([paste_text/2]).

init(Req, Opts) ->
	random:seed(now()),
	{cowboy_rest, Req, Opts}.

allowed_methods(Req, State) ->
	{[<<"GET">>, <<"POST">>], Req, State}.

content_types_provided(Req, State) ->
	{[
		{{<<"text">>, <<"plain">>, []}, paste_text},
		{{<<"text">>, <<"html">>, []}, paste_html}
	], Req, State}.

content_types_accepted(Req, State) ->
	{[{{<<"application">>, <<"x-www-form-urlencoded">>, []}, create_paste}],
		Req, State}.

resource_exists(Req, _State) ->
	case cowboy_req:binding(paste_id, Req) of
		undefined ->
			{true, Req, index};
		PasteID ->
			case valid_path(PasteID) and file_exists(PasteID) of
				true -> {true, Req, PasteID};
				false -> {false, Req, PasteID}
			end
	end.

create_paste(Req, State) ->
	PasteID = new_paste_id(),
	{ok, [{<<"paste">>, Paste}], Req2} = cowboy_req:body_qs(Req),
	ok = file:write_file(full_path(PasteID), Paste),
	case cowboy_req:method(Req2) of
		<<"POST">> ->
			{{true, <<$/, PasteID/binary>>}, Req2, State};
		_ ->
			{true, Req2, State}
	end.

paste_html(Req, index) ->
	{read_file("index.html"), Req, index};
paste_html(Req, Paste) ->
	#{lang := Lang} = cowboy_req:match_qs(Req, [lang]),
	{format_html(Paste, Lang), Req, Paste}.

paste_text(Req, index) ->
	{read_file("index.txt"), Req, index};
paste_text(Req, Paste) ->
	#{lang := Lang} = cowboy_req:match_qs(Req, [lang]),
	{format_text(Paste, Lang), Req, Paste}.

% Private

read_file(Name) ->
	{ok, Binary} = file:read_file(full_path(Name)),
	Binary.

full_path(Name) ->
	filename:join([code:priv_dir(rest_pastebin), Name]).

file_exists(Name) ->
	case file:read_file_info(full_path(Name)) of
		{ok, _Info} -> true;
		{error, _Reason} -> false
	end.

valid_path(<<>>) -> true;
valid_path(<<$., _T/binary>>) -> false;
valid_path(<<$/, _T/binary>>) -> false;
valid_path(<<_Char, T/binary>>) -> valid_path(T).

new_paste_id() ->
	Initial = random:uniform(62) - 1,
	new_paste_id(<<Initial>>, 7).
new_paste_id(Bin, 0) ->
	Chars = <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890">>,
	<< <<(binary_part(Chars, B, 1))/binary>> || <<B>> <= Bin >>;
new_paste_id(Bin, Rem) ->
	Next = random:uniform(62) - 1,
	new_paste_id(<<Bin/binary, Next>>, Rem - 1).

format_html(Paste, plain) ->
	Text = escape_html_chars(read_file(Paste)),
	<<"<!DOCTYPE html><html>",
	"<head><title>paste</title></head>",
	"<body><pre><code>", Text/binary, "</code></pre></body></html>\n">>;
format_html(Paste, Lang) ->
	highlight(full_path(Paste), Lang, "html").

format_text(Paste, plain) ->
	read_file(Paste);
format_text(Paste, Lang) ->
	highlight(full_path(Paste), Lang, "ansi").

highlight(Path, Lang, Type) ->
	Path1 = binary_to_list(Path),
	Lang1 = binary_to_list(Lang),
	os:cmd(["highlight --syntax=", Lang1,
		" --doc-title=paste ",
		" --out-format=", Type,
		" --include-style ", Path1]).

% Escape some HTML characters that might make a fuss
escape_html_chars(Bin) ->
	<< <<(escape_html_char(B))/binary>> || <<B>> <= Bin >>.

escape_html_char($<) -> <<"&lt;">>;
escape_html_char($>) -> <<"&gt;">>;
escape_html_char($&) -> <<"&amp;">>;
escape_html_char(C) -> <<C>>.