1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(observer_html_lib).
21
22%%
23%% This module implements the HTML generation for the crashdump
24%% viewer. No logic or states are kept by this module.
25%%
26
27-export([plain_page/2,
28	 expandable_term/4,
29	 warning/2]).
30
31-include("crashdump_viewer.hrl").
32-include("observer_defs.hrl").
33
34%%%-----------------------------------------------------------------
35%%% Display the given information as is, no heading
36%%% Empty body if no info exists.
37warning(Info, Colors0) ->
38    Colors = convert(Colors0),
39    header(body(warning_body(Info), Colors)).
40
41warning_body(Info) ->
42    [warn(Info)].
43
44%%%-----------------------------------------------------------------
45%%% Display the given information as is, no heading
46%%% Empty body if no info exists.
47plain_page(Info, Colors0) ->
48    Colors = convert(Colors0),
49    header(body(plain_body(Info), Colors)).
50
51plain_body(Info) ->
52    [pre(href_proc_port(lists:flatten(Info)))].
53
54%%%-----------------------------------------------------------------
55%%% Expanded memory
56expandable_term(Heading,Expanded,Tab, Colors0) ->
57    Colors = convert(Colors0),
58    header(Heading,
59           body(expandable_term_body(Heading,Expanded,Tab,Colors),
60                Colors)).
61
62expandable_term_body(Heading,[],_Tab, _) ->
63    [case Heading of
64	 "MsgQueue" -> "No messages were found";
65	 "Message Queue" -> "No messages were found";
66	 "StackDump"  -> "No stack dump was found";
67	 "Dictionary" -> "No dictionary was found";
68	 "ProcState"  -> "Information could not be retrieved,"
69			     " system messages may not be handled by this process.";
70         "SaslLog"    -> "No log entry was found";
71         "Persistent Terms" -> "No persistent terms were found"
72     end];
73expandable_term_body(Heading,Expanded,Tab, Colors) ->
74    Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%",
75    [case Heading of
76	 "MsgQueue" ->
77	     table(Attr,
78		   [tr(
79		      [th("WIDTH=70%","Message"),
80		       th("WIDTH=30%","SeqTraceToken")]) |
81		    element(1, lists:mapfoldl(fun(Msg, Even) ->
82						      {msgq_table(Tab, Msg, Even, Colors),
83						       not Even}
84					      end,
85					      true, Expanded))]);
86	 "Message Queue" ->
87	     table(Attr,
88		   [tr(
89		      [th("WIDTH=10%","Id"),
90		       th("WIDTH=90%","Message")]) |
91		    element(1, lists:mapfoldl(fun(Msg, {Even,N}) ->
92						      {msgq_table(Tab, Msg, N, Even, Colors),
93						       {not Even, N+1}}
94					      end,
95					      {true,1}, Expanded))]);
96	 "StackDump" ->
97	     table(Attr,
98		   [tr(
99		      [th("WIDTH=20%","Label"),
100		       th("WIDTH=80%","Term")]) |
101		    element(1, lists:mapfoldl(fun(Entry, Even) ->
102						      {stackdump_table(Tab, Entry, Even, Colors),
103						       not Even}
104					      end, true, Expanded))]);
105	 "ProcState" ->
106	     table(Attr,
107		   [tr(
108		      [th("WIDTH=20%","Label"),
109		       th("WIDTH=80%","Information")]) |
110		    element(1, lists:mapfoldl(fun(Entry, Even) ->
111						      {proc_state(Tab, Entry,Even, Colors),
112						       not Even}
113					      end, true, Expanded))]);
114     "SaslLog"  ->
115             table(Attr,
116                   [tr("BGCOLOR=white",[td("ALIGN=left", pre(href_proc_port(Expanded)))])]) ;
117	 _ ->
118	     table(Attr,
119		   [tr(
120		      [th("WIDTH=30%","Key"),
121		       th("WIDTH=70%","Value")]) |
122		    element(1, lists:mapfoldl(fun(Entry, Even) ->
123						      {dict_table(Tab, Entry, Even, Colors),
124						       not Even}
125					      end, true, Expanded))])
126     end].
127
128msgq_table(Tab,{Msg0,Token0}, Even, Colors) ->
129    Token = case Token0 of
130		[] -> "";
131		_ -> io_lib:fwrite("~w",[Token0])
132	    end,
133    Msg = all_or_expand(Tab,Msg0),
134    tr(color(Even, Colors),[td(pre(Msg)), td(Token)]).
135
136msgq_table(Tab,Msg0, Id, Even, Colors) ->
137    Msg = all_or_expand(Tab,Msg0),
138    tr(color(Even, Colors),[td(integer_to_list(Id)), td(pre(Msg))]).
139
140stackdump_table(Tab,{Label0,Term0},Even, Colors) ->
141    Label = io_lib:format("~ts",[Label0]),
142    Term = case atom_to_list(Label0) of
143               "y" ++ _ ->
144                   %% Any term is possible, including huge ones.
145                   all_or_expand(Tab,Term0);
146               _ ->
147                   %% Return address or catch tag. It is known to be a
148                   %% flat list, shortish, possibly containing characters
149                   %% greater than 255.
150                   href_proc_port(Term0)
151           end,
152    tr(color(Even, Colors), [td("VALIGN=center",pre(Label)), td(pre(Term))]).
153
154dict_table(Tab,{Key0,Value0}, Even, Colors) ->
155    Key = all_or_expand(Tab,Key0),
156    Value = all_or_expand(Tab,Value0),
157    tr(color(Even, Colors), [td("VALIGN=center",pre(Key)), td(pre(Value))]).
158
159proc_state(Tab,{Key0,Value0}, Even, Colors) ->
160    Key = lists:flatten(io_lib:format("~ts",[Key0])),
161    Value = all_or_expand(Tab,Value0),
162    tr(color(Even, Colors), [td("VALIGN=center",Key), td(pre(Value))]).
163
164all_or_expand(Tab,Term) ->
165    Preview = io_lib:format("~tP",[Term,8]),
166    Check = io_lib:format("~tP",[Term,100]),
167    Exp = Preview=/=Check,
168    all_or_expand(Tab,Term,Preview,Exp).
169all_or_expand(_Tab,Term,Str,false)
170  when not is_binary(Term) ->
171    href_proc_port(lists:flatten(Str));
172all_or_expand(Tab,Term,Preview,true)
173  when not is_binary(Term) ->
174    Key = {Key1,Key2,Key3} = {erlang:unique_integer([positive]),1,2},
175    ets:insert(Tab,{Key,Term}),
176    [href_proc_port(lists:flatten(Preview), false), $\n,
177     href("TARGET=\"expanded\"",
178	  ["#Term?key1="++integer_to_list(Key1)++
179	       "&key2="++integer_to_list(Key2)++
180	       "&key3="++integer_to_list(Key3)],
181	  "Click to expand above term")];
182all_or_expand(Tab,Bin,_PreviewStr,_Expand)
183  when is_binary(Bin) ->
184    OBSBin = observer_lib:make_obsbin(Bin,Tab),
185    Term = io_lib:format("~tp", [OBSBin]),
186    href_proc_port(lists:flatten(Term), true).
187
188color(true, #colors{even=Even}) -> "BGCOLOR="++Even;
189color(false,#colors{odd=Odd})   -> "BGCOLOR="++Odd.
190
191%%%-----------------------------------------------------------------
192%%% Internal library
193start_html() ->
194    "<HTML>\n".
195stop_html() ->
196    "</HTML>".
197start_html_body(#colors{even=Even, fg=Fg}) ->
198    "<BODY BGCOLOR=" ++ Even ++ ">\n <FONT COLOR=" ++ Fg ++ ">\n".
199stop_html_body() ->
200    "</FONT> </BODY>\n".
201
202header(Body) ->
203    header("","",Body).
204header(Title,Body) ->
205    header(Title,"",Body).
206header(Title,JavaScript,Body) ->
207    [%only_http_header(),
208     html_header(Title,JavaScript,Body)].
209
210html_header(Title,JavaScript,Body) ->
211    [start_html(),
212     only_html_header(Title,JavaScript),
213     Body,
214     stop_html()].
215
216only_html_header(Title,JavaScript) ->
217    ["<HEAD>\n",
218     "<TITLE>", Title,  "</TITLE>\n",
219     JavaScript,
220     "</HEAD>\n"].
221
222body(Text, Colors) ->
223    [start_html_body(Colors),
224     Text,
225     stop_html_body()].
226
227start_table(Args) ->
228    ["<TABLE ", Args, ">\n"].
229stop_table() ->
230    "</TABLE>\n".
231
232table(Args,Text) ->
233    [start_table(Args), Text, stop_table()].
234tr(Text) ->
235    ["<TR>\n", Text, "\n</TR>\n"].
236tr(Args,Text) ->
237    ["<TR ", Args, ">\n", Text, "\n</TR>\n"].
238th(Args,Text) ->
239    ["<TH ", Args, ">\n", Text, "\n</TH>\n"].
240td(Text) ->
241    ["<TD>", Text, "</TD>"].
242td(Args,Text) ->
243    ["<TD ", Args, ">", Text, "</TD>"].
244
245start_pre() ->
246    "<PRE>".
247stop_pre() ->
248    "</PRE>".
249pre(Text) ->
250    [start_pre(),Text,stop_pre()].
251href(Link,Text) ->
252    ["<A HREF=\"",Link,"\">",Text,"</A>"].
253href(Args,Link,Text) ->
254    ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"].
255font(Args,Text) ->
256    ["<FONT ",Args,">\n",Text,"\n</FONT>\n"].
257p(Text) ->
258    ["<P>",Text,"</P>\n"].
259br() ->
260    "<BR>\n".
261
262
263%% In all the following, "<" is changed to "&lt;" and ">" is changed to "&gt;"
264href_proc_port(Text) ->
265    href_proc_port(Text,true).
266href_proc_port(Text,LinkToBin) ->
267    href_proc_port(Text,[],LinkToBin).
268href_proc_port("#Ref<"++T,Acc,LTB) ->
269    %% No links to refs
270    href_proc_port(T,["#Ref&lt;"|Acc],LTB);
271href_proc_port("#Fun<"++T,Acc,LTB) ->
272    %% No links to funs
273    href_proc_port(T,["#Fun&lt;"|Acc],LTB);
274href_proc_port("#Port<"++T,Acc,LTB) ->
275    {Port0,Rest} = split($>,T),
276    Port = "#Port&lt;"++Port0 ++ "&gt;",
277    href_proc_port(Rest,[href(Port,Port)|Acc],LTB);
278href_proc_port("<<"++T,Acc,LTB) ->
279    %% No links to binaries
280    href_proc_port(T,["&lt;&lt;"|Acc],LTB);
281href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 ->
282    %% Pid
283    {Pid0,Rest} = split($>,T),
284    Pid = "&lt;" ++ Pid0 ++ "&gt",
285    href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB);
286href_proc_port("['#CDVBin'"++T,Acc,LTB) ->
287    %% Binary written by crashdump_viewer:parse_heap_term(...)
288    href_proc_bin(cdv, T, Acc, LTB);
289href_proc_port("['#OBSBin'"++T,Acc,LTB) ->
290    %% Binary written by crashdump_viewer:parse_heap_term(...)
291    href_proc_bin(obs, T, Acc, LTB);
292href_proc_port("['#CDVPort'"++T,Acc,LTB) ->
293    %% Port written by crashdump_viewer:parse_term(...)
294    {Port0,Rest} = split($],T),
295    PortStr=
296	case string:lexemes(Port0,",.|") of
297	    [X,Y] ->
298		Port = "#Port&lt;"++X++"."++Y++"&gt;",
299		href(Port,Port);
300	Ns ->
301		"#Port&lt;" ++ lists:join($.,Ns) ++"...&gt;"
302    end,
303    href_proc_port(Rest,[PortStr|Acc],LTB);
304href_proc_port("['#CDVPid'"++T,Acc,LTB) ->
305    %% Pid written by crashdump_viewer:parse_term(...)
306    {Pid0,Rest} = split($],T),
307    PidStr =
308	case string:lexemes(Pid0,",.|") of
309	    [X,Y,Z] ->
310		Pid = "&lt;"++X++"."++Y++"."++Z++"&gt;",
311		href(Pid,Pid);
312	    Ns ->
313		"&lt;" ++ lists:join($.,Ns) ++ "...&gt;"
314	end,
315    href_proc_port(Rest,[PidStr|Acc],LTB);
316href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)->
317    %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...)
318    IH = lists:reverse(
319	   lists:flatten(
320	     "<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")),
321    href_proc_port(T,IH++Acc,LTB);
322href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)->
323    %% A binary which is truncated! Written by
324    %% crashdump_viewer:parse_heap_term(...)
325    IH = lists:reverse(
326	   lists:flatten(
327	     "<FONT COLOR=\"#FF0000\">&lt;&lt;...(Truncated Binary)&gt;&gt;"
328	     "</FONT>")),
329    href_proc_port(T,IH++Acc,LTB);
330href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)->
331    %% A binary which could not be found in the dump! Written by
332    %% crashdump_viewer:parse_heap_term(...)
333    IH = lists:reverse(
334	   lists:flatten(
335	     "<FONT COLOR=\"#FF0000\">&lt;&lt;...(Nonexisting Binary)&gt;&gt;"
336	     "</FONT>")),
337    href_proc_port(T,IH++Acc,LTB);
338href_proc_port("<"++T,Acc,LTB) ->
339    href_proc_port(T,["&lt;"|Acc],LTB);
340href_proc_port(">"++T,Acc,LTB) ->
341    href_proc_port(T,["&gt;"|Acc],LTB);
342href_proc_port([H|T],Acc,LTB) ->
343    href_proc_port(T,[H|Acc],LTB);
344href_proc_port([],Acc,_) ->
345    lists:reverse(Acc).
346
347href_proc_bin(From, T, Acc, LTB) ->
348    {OffsetSizePos,Rest} = split($],T),
349    BinStr =
350	case string:lexemes(OffsetSizePos,",.| \n") of
351	    [Offset,SizeStr,Pos] when From =:= cdv ->
352                Size = list_to_integer(SizeStr),
353                PreviewSize = min(Size,10),
354		Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)},
355                case crashdump_viewer:expand_binary(Id) of
356                    {ok, '#CDVTruncatedBinary'} ->
357                          lists:flatten(
358                            "<FONT COLOR=\"#FF0000\">"
359                            "&lt;&lt;...(Truncated Binary)&gt;&gt;"
360                            "</FONT>");
361                    {ok, PreviewBin} ->
362                        PreviewStr = preview_string(Size, PreviewBin),
363                        if LTB ->
364                                href("TARGET=\"expanded\"",
365                                     ["#Binary?offset="++Offset++
366                                          "&size="++SizeStr++
367                                          "&pos="++Pos],
368                                     PreviewStr);
369                           true ->
370                                PreviewStr
371                        end
372                end;
373	    [PreviewIntStr,PreviewBitSizeStr,SizeStr,Md5] when From =:= obs ->
374		Size = list_to_integer(SizeStr),
375                PreviewInt = list_to_integer(PreviewIntStr),
376                PreviewBitSize = list_to_integer(PreviewBitSizeStr),
377		PreviewStr = preview_string(Size,<<PreviewInt:PreviewBitSize>>),
378		if LTB ->
379			href("TARGET=\"expanded\"",
380			     ["#OBSBinary?key1="++PreviewIntStr++
381				  "&key2="++SizeStr++
382				  "&key3="++Md5],
383			     PreviewStr);
384		   true ->
385			PreviewStr
386		end;
387	    _ ->
388		"&lt;&lt; ... &gt;&gt;"
389	end,
390    href_proc_port(Rest,[BinStr|Acc],LTB).
391
392preview_string(Size, PreviewBin) when Size > 10 ->
393    ["&lt;&lt;",
394     remove_lgt(io_lib:format("~tp",[PreviewBin])),
395     "...(",
396     observer_lib:to_str({bytes,Size}),
397     ")",
398     "&gt;&gt"];
399preview_string(_, PreviewBin) ->
400    ["&lt;&lt;",
401     remove_lgt(io_lib:format("~tp",[PreviewBin])),
402     "&gt;&gt"].
403
404remove_lgt(Deep) ->
405    remove_lgt_1(lists:flatten(Deep)).
406
407remove_lgt_1([$<,$<|Rest]) ->
408    [$>,$>|BinStr] = lists:reverse(Rest),
409    replace_lgt(lists:reverse(BinStr));
410remove_lgt_1(TruncBin) ->
411    TruncBin.
412
413replace_lgt([$<|R]) ->
414    ["&lt;"|replace_lgt(R)];
415replace_lgt([$>|R]) ->
416    ["&gt;"|replace_lgt(R)];
417replace_lgt([L=[_|_]|R]) ->
418    [replace_lgt(L)|replace_lgt(R)];
419replace_lgt([A|R]) ->
420    [A|replace_lgt(R)];
421replace_lgt([]) -> [].
422
423split(Char,Str) ->
424    split(Char,Str,[]).
425split(Char,[Char|Str],Acc) -> % match Char
426    {lists:reverse(Acc),Str};
427split(Char,[H|T],Acc) ->
428    split(Char,T,[H|Acc]).
429
430warn([]) ->
431    [];
432warn(Warning) ->
433    font("COLOR=\"#FF0000\"",p([Warning,br(),br()])).
434
435convert(#colors{fg={FR,FB,FG}, even={ER,EB,EG}, odd={OR,OG,OB}}) ->
436    #colors{fg   = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [FR,FB,FG]),
437            even = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [ER,EB,EG]),
438            odd  = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [OR,OG,OB])}.
439