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("~w",[Label0]),
142    Term = all_or_expand(Tab,Term0),
143    tr(color(Even, Colors), [td("VALIGN=center",pre(Label)), td(pre(Term))]).
144
145dict_table(Tab,{Key0,Value0}, Even, Colors) ->
146    Key = all_or_expand(Tab,Key0),
147    Value = all_or_expand(Tab,Value0),
148    tr(color(Even, Colors), [td("VALIGN=center",pre(Key)), td(pre(Value))]).
149
150proc_state(Tab,{Key0,Value0}, Even, Colors) ->
151    Key = lists:flatten(io_lib:format("~ts",[Key0])),
152    Value = all_or_expand(Tab,Value0),
153    tr(color(Even, Colors), [td("VALIGN=center",Key), td(pre(Value))]).
154
155all_or_expand(Tab,Term) ->
156    Preview = io_lib:format("~tP",[Term,8]),
157    Check = io_lib:format("~tP",[Term,100]),
158    Exp = Preview=/=Check,
159    all_or_expand(Tab,Term,Preview,Exp).
160all_or_expand(_Tab,Term,Str,false)
161  when not is_binary(Term) ->
162    href_proc_port(lists:flatten(Str));
163all_or_expand(Tab,Term,Preview,true)
164  when not is_binary(Term) ->
165    Key = {Key1,Key2,Key3} = {erlang:unique_integer([positive]),1,2},
166    ets:insert(Tab,{Key,Term}),
167    [href_proc_port(lists:flatten(Preview), false), $\n,
168     href("TARGET=\"expanded\"",
169	  ["#Term?key1="++integer_to_list(Key1)++
170	       "&key2="++integer_to_list(Key2)++
171	       "&key3="++integer_to_list(Key3)],
172	  "Click to expand above term")];
173all_or_expand(Tab,Bin,_PreviewStr,_Expand)
174  when is_binary(Bin) ->
175    OBSBin = observer_lib:make_obsbin(Bin,Tab),
176    Term = io_lib:format("~tp", [OBSBin]),
177    href_proc_port(lists:flatten(Term), true).
178
179color(true, #colors{even=Even}) -> "BGCOLOR="++Even;
180color(false,#colors{odd=Odd})   -> "BGCOLOR="++Odd.
181
182%%%-----------------------------------------------------------------
183%%% Internal library
184start_html() ->
185    "<HTML>\n".
186stop_html() ->
187    "</HTML>".
188start_html_body(#colors{even=Even, fg=Fg}) ->
189    "<BODY BGCOLOR=" ++ Even ++ ">\n <FONT COLOR=" ++ Fg ++ ">\n".
190stop_html_body() ->
191    "</FONT> </BODY>\n".
192
193header(Body) ->
194    header("","",Body).
195header(Title,Body) ->
196    header(Title,"",Body).
197header(Title,JavaScript,Body) ->
198    [%only_http_header(),
199     html_header(Title,JavaScript,Body)].
200
201html_header(Title,JavaScript,Body) ->
202    [start_html(),
203     only_html_header(Title,JavaScript),
204     Body,
205     stop_html()].
206
207only_html_header(Title,JavaScript) ->
208    ["<HEAD>\n",
209     "<TITLE>", Title,  "</TITLE>\n",
210     JavaScript,
211     "</HEAD>\n"].
212
213body(Text, Colors) ->
214    [start_html_body(Colors),
215     Text,
216     stop_html_body()].
217
218start_table(Args) ->
219    ["<TABLE ", Args, ">\n"].
220stop_table() ->
221    "</TABLE>\n".
222
223table(Args,Text) ->
224    [start_table(Args), Text, stop_table()].
225tr(Text) ->
226    ["<TR>\n", Text, "\n</TR>\n"].
227tr(Args,Text) ->
228    ["<TR ", Args, ">\n", Text, "\n</TR>\n"].
229th(Args,Text) ->
230    ["<TH ", Args, ">\n", Text, "\n</TH>\n"].
231td(Text) ->
232    ["<TD>", Text, "</TD>"].
233td(Args,Text) ->
234    ["<TD ", Args, ">", Text, "</TD>"].
235
236start_pre() ->
237    "<PRE>".
238stop_pre() ->
239    "</PRE>".
240pre(Text) ->
241    [start_pre(),Text,stop_pre()].
242href(Link,Text) ->
243    ["<A HREF=\"",Link,"\">",Text,"</A>"].
244href(Args,Link,Text) ->
245    ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"].
246font(Args,Text) ->
247    ["<FONT ",Args,">\n",Text,"\n</FONT>\n"].
248p(Text) ->
249    ["<P>",Text,"</P>\n"].
250br() ->
251    "<BR>\n".
252
253
254%% In all the following, "<" is changed to "&lt;" and ">" is changed to "&gt;"
255href_proc_port(Text) ->
256    href_proc_port(Text,true).
257href_proc_port(Text,LinkToBin) ->
258    href_proc_port(Text,[],LinkToBin).
259href_proc_port("#Ref<"++T,Acc,LTB) ->
260    %% No links to refs
261    href_proc_port(T,["#Ref&lt;"|Acc],LTB);
262href_proc_port("#Fun<"++T,Acc,LTB) ->
263    %% No links to funs
264    href_proc_port(T,["#Fun&lt;"|Acc],LTB);
265href_proc_port("#Port<"++T,Acc,LTB) ->
266    {Port0,Rest} = split($>,T),
267    Port = "#Port&lt;"++Port0 ++ "&gt;",
268    href_proc_port(Rest,[href(Port,Port)|Acc],LTB);
269href_proc_port("<<"++T,Acc,LTB) ->
270    %% No links to binaries
271    href_proc_port(T,["&lt;&lt;"|Acc],LTB);
272href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 ->
273    %% Pid
274    {Pid0,Rest} = split($>,T),
275    Pid = "&lt;" ++ Pid0 ++ "&gt",
276    href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB);
277href_proc_port("['#CDVBin'"++T,Acc,LTB) ->
278    %% Binary written by crashdump_viewer:parse_heap_term(...)
279    href_proc_bin(cdv, T, Acc, LTB);
280href_proc_port("['#OBSBin'"++T,Acc,LTB) ->
281    %% Binary written by crashdump_viewer:parse_heap_term(...)
282    href_proc_bin(obs, T, Acc, LTB);
283href_proc_port("['#CDVPort'"++T,Acc,LTB) ->
284    %% Port written by crashdump_viewer:parse_term(...)
285    {Port0,Rest} = split($],T),
286    PortStr=
287	case string:lexemes(Port0,",.|") of
288	    [X,Y] ->
289		Port = "#Port&lt;"++X++"."++Y++"&gt;",
290		href(Port,Port);
291	Ns ->
292		"#Port&lt;" ++ lists:join($.,Ns) ++"...&gt;"
293    end,
294    href_proc_port(Rest,[PortStr|Acc],LTB);
295href_proc_port("['#CDVPid'"++T,Acc,LTB) ->
296    %% Pid written by crashdump_viewer:parse_term(...)
297    {Pid0,Rest} = split($],T),
298    PidStr =
299	case string:lexemes(Pid0,",.|") of
300	    [X,Y,Z] ->
301		Pid = "&lt;"++X++"."++Y++"."++Z++"&gt;",
302		href(Pid,Pid);
303	    Ns ->
304		"&lt;" ++ lists:join($.,Ns) ++ "...&gt;"
305	end,
306    href_proc_port(Rest,[PidStr|Acc],LTB);
307href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)->
308    %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...)
309    IH = lists:reverse(
310	   lists:flatten(
311	     "<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")),
312    href_proc_port(T,IH++Acc,LTB);
313href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)->
314    %% A binary which is truncated! Written by
315    %% crashdump_viewer:parse_heap_term(...)
316    IH = lists:reverse(
317	   lists:flatten(
318	     "<FONT COLOR=\"#FF0000\">&lt;&lt;...(Truncated Binary)&gt;&gt;"
319	     "</FONT>")),
320    href_proc_port(T,IH++Acc,LTB);
321href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)->
322    %% A binary which could not be found in the dump! Written by
323    %% crashdump_viewer:parse_heap_term(...)
324    IH = lists:reverse(
325	   lists:flatten(
326	     "<FONT COLOR=\"#FF0000\">&lt;&lt;...(Nonexisting Binary)&gt;&gt;"
327	     "</FONT>")),
328    href_proc_port(T,IH++Acc,LTB);
329href_proc_port("<"++T,Acc,LTB) ->
330    href_proc_port(T,["&lt;"|Acc],LTB);
331href_proc_port(">"++T,Acc,LTB) ->
332    href_proc_port(T,["&gt;"|Acc],LTB);
333href_proc_port([H|T],Acc,LTB) ->
334    href_proc_port(T,[H|Acc],LTB);
335href_proc_port([],Acc,_) ->
336    lists:reverse(Acc).
337
338href_proc_bin(From, T, Acc, LTB) ->
339    {OffsetSizePos,Rest} = split($],T),
340    BinStr =
341	case string:lexemes(OffsetSizePos,",.| \n") of
342	    [Offset,SizeStr,Pos] when From =:= cdv ->
343                Size = list_to_integer(SizeStr),
344                PreviewSize = min(Size,10),
345		Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)},
346                case crashdump_viewer:expand_binary(Id) of
347                    {ok, '#CDVTruncatedBinary'} ->
348                          lists:flatten(
349                            "<FONT COLOR=\"#FF0000\">"
350                            "&lt;&lt;...(Truncated Binary)&gt;&gt;"
351                            "</FONT>");
352                    {ok, PreviewBin} ->
353                        PreviewStr = preview_string(Size, PreviewBin),
354                        if LTB ->
355                                href("TARGET=\"expanded\"",
356                                     ["#Binary?offset="++Offset++
357                                          "&size="++SizeStr++
358                                          "&pos="++Pos],
359                                     PreviewStr);
360                           true ->
361                                PreviewStr
362                        end
363                end;
364	    [PreviewIntStr,PreviewBitSizeStr,SizeStr,Md5] when From =:= obs ->
365		Size = list_to_integer(SizeStr),
366                PreviewInt = list_to_integer(PreviewIntStr),
367                PreviewBitSize = list_to_integer(PreviewBitSizeStr),
368		PreviewStr = preview_string(Size,<<PreviewInt:PreviewBitSize>>),
369		if LTB ->
370			href("TARGET=\"expanded\"",
371			     ["#OBSBinary?key1="++PreviewIntStr++
372				  "&key2="++SizeStr++
373				  "&key3="++Md5],
374			     PreviewStr);
375		   true ->
376			PreviewStr
377		end;
378	    _ ->
379		"&lt;&lt; ... &gt;&gt;"
380	end,
381    href_proc_port(Rest,[BinStr|Acc],LTB).
382
383preview_string(Size, PreviewBin) when Size > 10 ->
384    ["&lt;&lt;",
385     remove_lgt(io_lib:format("~tp",[PreviewBin])),
386     "...(",
387     observer_lib:to_str({bytes,Size}),
388     ")",
389     "&gt;&gt"];
390preview_string(_, PreviewBin) ->
391    ["&lt;&lt;",
392     remove_lgt(io_lib:format("~tp",[PreviewBin])),
393     "&gt;&gt"].
394
395remove_lgt(Deep) ->
396    remove_lgt_1(lists:flatten(Deep)).
397
398remove_lgt_1([$<,$<|Rest]) ->
399    [$>,$>|BinStr] = lists:reverse(Rest),
400    replace_lgt(lists:reverse(BinStr));
401remove_lgt_1(TruncBin) ->
402    TruncBin.
403
404replace_lgt([$<|R]) ->
405    ["&lt;"|replace_lgt(R)];
406replace_lgt([$>|R]) ->
407    ["&gt;"|replace_lgt(R)];
408replace_lgt([L=[_|_]|R]) ->
409    [replace_lgt(L)|replace_lgt(R)];
410replace_lgt([A|R]) ->
411    [A|replace_lgt(R)];
412replace_lgt([]) -> [].
413
414split(Char,Str) ->
415    split(Char,Str,[]).
416split(Char,[Char|Str],Acc) -> % match Char
417    {lists:reverse(Acc),Str};
418split(Char,[H|T],Acc) ->
419    split(Char,T,[H|Acc]).
420
421warn([]) ->
422    [];
423warn(Warning) ->
424    font("COLOR=\"#FF0000\"",p([Warning,br(),br()])).
425
426convert(#colors{fg={FR,FB,FG}, even={ER,EB,EG}, odd={OR,OG,OB}}) ->
427    #colors{fg   = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [FR,FB,FG]),
428            even = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [ER,EB,EG]),
429            odd  = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [OR,OG,OB])}.
430