/* This file is part of Bolixo. Bolixo 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 3 of the License, or (at your option) any later version. Bolixo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 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 Bolixo. If not, see . */ #include #include #include #include #include #include #include #include #include #define DEFINE_TBFTYPE #include "../bolixo.h" #define DEFINE_USERINFO #include "util.h" #include #include "../bolixo.m" #include #define INSTRUMENT_EXTERN #include "../instrument.h" #include "w_var.h" #include "steps.h" using namespace std; USERLOGINFO userinfo; string w_session; #define bo_sessiond_client_test_NOTNEED #define bo_sessiond_client_getsessioninfovars_NOTNEED #define bo_sessiond_client_getsessioninfovars_v2_NOTNEED #define bo_sessiond_client_waitevent_NOTNEED #define bo_sessiond_client_setnotify_NOTNEED #define bo_sessiond_client_ping_NOTNEED #define bod_client_createsession_NOTNEED #define bod_client_aboutme_NOTNEED #define bod_client_login_NOTNEED #define bod_client_logout_NOTNEED #define bod_client_adduser_NOTNEED #define bod_client_confirmuser_NOTNEED #define bod_client_deleteuser_NOTNEED #define bod_client_confirmdelete_NOTNEED #define bod_client_addfile_NOTNEED #define bod_client_addfile_bob_NOTNEED #define bod_client_appendfile_NOTNEED #define bod_client_delfile_NOTNEED #define bod_client_undelete_NOTNEED #define bod_client_modifyfile_NOTNEED #define bod_client_modifyfile_bob_NOTNEED #define bod_client_rename_NOTNEED #define bod_client_copy_NOTNEED #define bod_client_readfile_NOTNEED #define bod_client_mkdir_NOTNEED #define bod_client_rmdir_NOTNEED #define bod_client_listdir_NOTNEED #define bod_client_set_access_NOTNEED #define bod_client_create_group_list_NOTNEED #define bod_client_create_group_NOTNEED #define bod_client_set_group_NOTNEED #define bod_client_set_member_NOTNEED #define bod_client_delete_list_NOTNEED #define bod_client_delete_group_NOTNEED #define bod_client_list_lists_NOTNEED #define bod_client_list_groups_NOTNEED #define bod_client_list_inboxes_NOTNEED #define bod_client_list_msgs_NOTNEED #define bod_client_sendmsg_NOTNEED #define bod_client_sendmsg_project_NOTNEED #define bod_client_replymsg_NOTNEED #define bod_client_replymsg_project_NOTNEED #define bod_client_sendattach_NOTNEED #define bod_client_create_project_dir_NOTNEED #define bod_client_verifysign_NOTNEED #define bod_client_sendtalk_NOTNEED #define bod_client_list_talk_NOTNEED #define bod_client_contact_list_NOTNEED #define bod_client_public_checkuser_NOTNEED #define bod_client_public_listdir_NOTNEED #define bod_client_public_list_talk_NOTNEED #define bod_client_config_write_NOTNEED #define bod_client_config_read_NOTNEED #define bod_client_contact_manage_NOTNEED #define bod_client_contact_request_NOTNEED #define bod_client_sendtalk_file_NOTNEED #define bod_client_set_group_desc_NOTNEED #define bod_client_set_list_desc_NOTNEED #define bod_client_form_readvar_NOTNEED #define bod_client_form_savevar_NOTNEED #define bod_client_form_deletevar_NOTNEED #define bod_client_form_deleteall_NOTNEED #define bod_client_interest_set_NOTNEED #define bod_client_interest_unset_NOTNEED #define bod_client_interest_list_NOTNEED #define bod_client_interest_check_NOTNEED #define bod_client_info_read_NOTNEED #define bod_client_info_write_NOTNEED #define bod_client_systempubkey_NOTNEED #define bod_client_systemsign_NOTNEED #define bod_client_registernode_NOTNEED #define bod_client_getpubkey_NOTNEED #define bod_client_remotelogin_NOTNEED #define bod_client_remotepass_NOTNEED #define bod_client_nodelogin_NOTNEED #define bod_client_nodepass_NOTNEED #define bod_client_remote_interest_set_NOTNEED #define bod_client_remote_interest_unset_NOTNEED #define bod_client_sendtalk_anon_NOTNEED #define bod_client_list_contacts_NOTNEED #define bod_client_get_notification_NOTNEED #define bod_client_set_notification_NOTNEED #define bod_client_playstep_NOTNEED #define bod_client_playstep_more_NOTNEED #define bod_client_set_members_NOTNEED #define bod_client_contact_remove_NOTNEED #define bod_client_waitevent_NOTNEED #define bod_client_list_members_NOTNEED #include "../proto/bod_client.protoch" #define bo_sessiond_client_getsessioninfo_NOTNEED #define bo_sessiond_client_setvar_NOTNEED #include "../proto/bo-sessiond_client.protoch" W_UNSIGNED w_robot ("robot"); W_UNSIGNED w_showmsg ("showmsg"); W_SSTRING w_file("file"); static float small_size=0.9; static unsigned draw_tab_height() { unsigned fontsize = tlmpweb_fontsize(); unsigned height = fontsize*1.3*small_size; if (height < 20) height = 20; return height+2; // Keep some space at the top } unsigned draw_tab ( const char *id_suffix, // id suffix to assemble the ID of the SVG path unsigned width, // TAB width or 0 (computed from the title) const char *fill, // Fill color const char *fill_in, // Fill color for mouseover bool close, // Close the path or not const char *title, bool drawx, // Put an X at the end (normally to close the TAB) PARAM_STRING href, // URL when the TAB is clicked PARAM_STRING xref) // URL when the X is clicked { unsigned height = draw_tab_height(); static STATIC_ID alloc_id; unsigned idnum = alloc_id++; /- if (width == 0){ width = tlmpweb_displaylen(title,small_size)+5; // See the .small css entry above // +5 because the server fonts do not match perfectly if (drawx){ width += 10+5+15; }else{ // We put 10 pixels on each side width += 10+10; } } htmlprintf ("\n",width,height); htmlprintf ("\n"); if (drawx && !tlmpweb_ismobile()){ unsigned x=width-17; htmlprintf ("\n",xref.ptr,idnum,idnum); // First rect is there to make the a tag larger. fill=none does not work. So we have to give it the same color // as the tab and changefill2 will change the color of the tab and this rect. // The second rect is used to highlit the X htmlprintf ("\n",ids.c_str(),x-5,4,height-6,fill); unsigned hmiddle = height/2+2; unsigned h14 = height-8; unsigned h8=height/3; if (h8 & 1) h8++; // Make sure h8 is even. htmlprintf ("\n",idnum,x-3,4,h14,height-4); htmlprintf ("\n" ,x,hmiddle-h8/2,h8,h8 ,x,hmiddle+h8/2,h8,h8); htmlout ("\n"); } htmlout ("\n"); return width; } // Same thing with no X unsigned draw_tab (const char *id_suffix, unsigned width, const char *fill, const char *fill_in, bool close, const char *title, PARAM_STRING href) { return draw_tab (id_suffix,width,fill,fill_in,close,title,false,href,""); } void draw_left_arrow(bool visible, PARAM_STRING xref) { unsigned height = draw_tab_height(); htmlprintf ("\n",height); if (visible){ static STATIC_ID alloc_id; unsigned id=alloc_id++; htmlprintf ("\n",xref.ptr,id,id); htmlprintf ("\n",id,height-4); } const char *color = visible ? "black" : "white"; unsigned h2=height/2; htmlprintf ("\n",color,color,h2,h2-8,h2+8); if (visible) htmlout ("\n"); // Always put the underline htmlprintf ("\n",height); htmlout ("\n"); } void draw_right_arrow(bool visible, PARAM_STRING xref) { unsigned height = draw_tab_height(); htmlprintf ("\n",height); if (visible){ static STATIC_ID alloc_id; unsigned id=alloc_id++; htmlprintf ("\n",xref.ptr,id,id); htmlprintf ("\n",id,height-4); } const char *color = visible ? "black" : "white"; unsigned h2=height/2; htmlprintf ("\n",color,color,h2,h2-8,h2+8); if (visible) htmlout ("\n"); // Always put the underline htmlprintf("\n",height); htmlout ("\n"); } static void print_href_c (const char *id_suffix, const char *title, PARAM_STRING href, const char *color, bool close) { draw_tab (id_suffix,0,color,color,close,title,href); } void print_href (const char *id_suffix,const char *title, PARAM_STRING href, bool notify) { print_href_c(id_suffix,title, href,notify ? "orange" : "#EAEAEA",true); } void print_aref (const char *id_suffix, const char *page, const char *title, int step, bool notify) { string href = string_f ("%s?webstep=%d",page,step); print_href (id_suffix,title,href,notify); } void print_aref (const char *id_suffix,const char *title, int step, bool notify) { print_aref (id_suffix,tlmpweb_curpage(),title,step,notify); } void print_aref_selected (const char *id_suffix, const char *page, const char *title, int step, bool notify) { string href = string_f ("%s?webstep=%d",page,step); print_href_c (id_suffix,title,href,notify ? "orange" : "white", false); } void print_aref_selected (const char *id_suffix, const char *title, int step, bool notify) { print_aref_selected (id_suffix,tlmpweb_curpage(),title,step,notify); } const char *format_line (const char *s) { while (*s != '\0' && *s != '\n'){ char car = *s++; if (car == '<'){ htmlout ("<"); }else if (car == '>'){ htmlout (">"); }else{ htmlout (car); } } return s; } const char *format_url (const char *s) { if (strncmp(s,"http://",7)!=0 && strncasecmp(s,"https://",8)!=0) htmlout ("http://"); return format_line (s); } void format_href(const char *s) { htmlout (""); if (strlen(s) > 50){ string tmp = string(s,80) + "..."; format_url(tmp.c_str()); }else{ format_url(s); } htmlout(""); } void format_content (const char *s, int nbline, bool &more) { more = false; bool ol_on = false; bool ul_on = false; bool quote_on = false; int noline = 0; while (*s != '\0' && noline < nbline){ if (*s == '\n'){ if (ul_on){ htmlout (""); ul_on = false; }else if (ol_on){ htmlout (""); ol_on = false; }else if (quote_on){ htmlout (""); quote_on = false; } htmlout ("

\n"); s++; noline++; }else{ const char *closing = ""; if (*s == '*'){ if (ol_on){ ol_on = false; htmlout (""); } if (!ul_on){ htmlout ("

    "); ul_on = true; } htmlout ("
  • "); s++; }else if (*s == '#'){ if (ul_on){ htmlout ("
"); ul_on = false; } if (!ol_on){ htmlout ("
    "); ol_on = true; } htmlout ("
  1. "); s++; }else if (*s == '?'){ s++; while (*s == ' ') s++; htmlout (""); s = format_url (s); htmlout (""); }else if (*s == '!'){ s++; if (*s == '!'){ s++; htmlout ("

    "); closing = "

    "; }else{ htmlout (""); closing = ""; } }else if (*s == '>'){ quote_on=true; s++; htmlout ("
    "); } s = format_line (s); noline++; htmlout (closing); if (*s == '\n'){ s++; htmlout ('\n'); } } } if (ul_on){ htmlout (""); } if (ol_on){ htmlout ("
"); } if (*s != '\0') more = true; } void format_content (const char *s) { bool more; format_content(s,10000,more); } void formatting_tips() { printhref ("/marker.html","Formatting tips",false); } void util_google_code() { #if 0 htmlout ("\n"); htmlout ("\n"); #endif } static unsigned mobile_body_font_size=300; static unsigned mobile_input_font_size=50; static unsigned mobile_button_font_size=80; void util_setmobilespecs (unsigned body_font_size, unsigned input_font_size, unsigned button_font_size) { mobile_body_font_size = body_font_size; mobile_input_font_size = input_font_size; mobile_button_font_size = button_font_size; } /* The experimental flag is set in the web container in /etc/bolixonode.conf. It is on in the development version. This allows for development of long and complex features, kept invisibles from users until they are ready. */ static bool experimental=false; void util_set_experimental() { experimental = true; } bool util_experimental() { return experimental; } /* Javascript needed at the end of the HTML fixsize_delay introduces a 2 seconds delay in the fixsize() function. We can see more clearly the effect it has. */ void util_endscript(bool fixsize_delay, PARAM_STRING urlparam) { // Script to make sure the webtabs tabs use the full height of the screen // Then it takes the webtables and grows them too. var inside_onread = false; document.onreadystatechange = function () { if (!inside_onread){ inside_onread = true; var state = document.readyState; if (state == 'interactive') { fixsize(); } else if (state == 'complete') { /- tlmpweb_position_popup_js(); } inside_onread = false; } }; var diff = 0; function allDescendants (node,total) { //console.log ('Enter ' + node.className + ' ' + node.id); if (node.className == "tabs" || node.className == "subtabs"){ var bord=(node.offsetHeight-node.clientHeight); //var style = node.currentStyle || window.getComputedStyle(node); var style = window.getComputedStyle(node); var margins = parseInt(style.marginTop,10) + parseInt(style.marginBottom,10); var paddings = parseInt(style.paddingTop,10) + parseInt(style.paddingBottom,10); total += bord + margins + paddings; console.log ("border cls=" + node.className + " node.id=" + node.id + " total="+total+" bord="+bord + " margins="+margins+ " paddings="+paddings); if (node.className == "subtabs"){ console.log ("Set subtabs "+node.id+" total="+total+" oldheight="+node.offsetHeight); node.style.height = diff - total; } } var last_webtable = null; for (var i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; if (child.id != null && (child.id == "tabs" || child.id == "tab_form" || child.id == "webtable-top")){ var add = child.offsetHeight; var style = window.getComputedStyle(child); add += parseInt(style.getPropertyValue('margin-top'),10); add += parseInt(style.getPropertyValue('margin-bottom'),10); var border_width = style.getPropertyValue('border-top-width'); if (child.id == "tab_form") add += 2; // Bug scrollbar ??? console.log ("child.id="+child.id+" add="+add+" border-width="+border_width); total += add; console.log ("child.id=" + child.id + " total="+total+" height="+child.offsetHeight); }else{ if (child.id != null && child.id == "vframe2h"){ var bord=(child.offsetWidth-child.clientWidth); total += bord; console.log ("border child.id=" + child.id + " total="+total+" border="+bord); total = allDescendants(child,total); }else if (child.className == "webtable" || child.className == "textgrow"){ //const newheight = diff - total; //console.log ("Set webtable "+child.id+" total="+total+" newheight="+newheight+" oldheight="+child.offsetHeight); //child.style.height = newheight; last_webtable = child; }else{ total = allDescendants(child,total); } } } if (last_webtable != null){ console.log ("last_webtable "+last_webtable.id+"="+add); const newheight = diff - total; console.log ("last Set webtable "+last_webtable.id+" total="+total+" newheight="+newheight+" oldheight="+last_webtable.offsetHeight); last_webtable.style.height = newheight; } return total; } ?> if (fixsize_delay){ setTimeout(resolve, ms)); } async function fixsize(){ await sleep(2000); ?> }else{ /- function fixsize(){ } webtable_setscroll(); if (!tlmpweb_isrobot() && tlmpweb_some_geometry_missing()){ if (tlmpweb_is_repost()){ tlmp_error ("util_endscript geometry_missing repost=1"); }else if (0){ tlmp_warning ("some geometry missing"); }else{ const char *sep1 = "?"; const char *sep2 = "&"; if (urlparam.ptr[0] == '\0'){ sep1 = sep2 = ""; } //tlmp_warning ("geometry_missing formsubmit, doing repost\n"); htmlprintf ("formsubmit(\"%s%s%s&repost=1\");\n",tlmpweb_curpage(),sep1,urlparam.ptr); } } /* Function used to toggle between the full and short version of a long message. We simply connect back to server using the UUID of the message. The server will send the proper long or short message and we just have to replace the old. We also have to make this row the active one and de-active the previous one. This function knows that a messages is presented as two or three rows. A selected message has lightblue as a background color, for all its row. All the TR in a message have the same onclick property (generated by wentable). After find the TR tag owning the message, we find the parent TABLE. Then we process all the TR entries of the table. We flip all trs having lightblue as a background color to ghostwhite. We flip the selected TRs having the same onclick to lightblue. This function is also used to just select this row. In this case, it sends select= instead of fullt= */ response.text()).then(function(content){\n",tlmpweb_curpage()); const elm = document.getElementById(id); if (elm){ elm.innerHTML = content; var parent = elm.parentNode; var tr = null; while (parent){ if (parent.tagName === 'TR'){ tr = parent; }else if (parent.tagName === 'TABLE'){ break; } parent = parent.parentNode; } if (parent){ const trs = parent.querySelectorAll('TR'); const onclick = String(tr.onclick); for (var i=0; i ?> } void util_meta() { htmlout ("\n"); } void util_defstyles() { unsigned body_font_size=85; unsigned input_font_size=100; unsigned button_font_size=90; unsigned input_checkbox=1; bool ismobile = tlmpweb_ismobile(); if (ismobile){ body_font_size = mobile_body_font_size; input_font_size = mobile_input_font_size; button_font_size = mobile_button_font_size; small_size = 1.0; input_checkbox = 3; } htmlout ("\n"); tlmpweb_setscripts(); htmlout ("\n"); } /* Draw the dot menu (3 bar in fact :-) ) only. No action or script attached. */ void util_draw_dotmenu(const char *id_menu, bool is_active, bool notify) { unsigned height = draw_tab_height(); unsigned width = height; unsigned thick = 2; unsigned line_y = height/4; unsigned skip_y = height/4; unsigned line_x1 = 5; unsigned line_x2 = width-5; if (tlmpweb_ismobile()){ width *= 2; thick = 4; line_x1 = 20; line_x2 = width-20; } htmlprintf ("\n",width,height); htmlprintf ("\n",id_menu,width,height,notify ? "orange" : "white"); for (int i=0; i<3; i++){ htmlprintf ("\n" ,line_x1,line_y,line_x2,line_y,thick); line_y += skip_y; } htmlprintf ("",is_active ? "white" : "black",height,width); htmlout (""); } /* Draw a 3 dots menu and the associated popup. */ void util_dotmenu (const vector &menu, bool is_active, bool notify) { static const char *id_dropdown = "dropdown-dot"; static const char *id_menu = "dotmenu"; unsigned popup_width = 0; { DIV d("dropdown"); d.print(); DIV dd("dropdown-content",id_dropdown); dd.print(); for (auto &m:menu){ htmlprintf ("%s\n",tlmpweb_curpage(),m.step,m.menu); unsigned menu_width = tlmpweb_displaylen(m.menu)+20; if (menu_width > popup_width) popup_width = menu_width; } } DIV d; d.id(id_menu).print(); htmlprintf ("\n",id_dropdown,id_menu,popup_width); util_draw_dotmenu (id_menu,is_active,notify); htmlout ("\n"); } void printhref(const char *url, const char *text) { htmlprintf ("
 %s 
",url,text); } void printhref_selected(const char *url, const char *text) { htmlprintf ("
 %s 
",url,text); } void printhref(const char *url, const char *text, bool largewindow) { if (w_robot){ htmlprintf ("%s",url,text); }else{ const char *script = largewindow ? "popup_large" : "popup_small"; htmlprintf ("
 %s 
",script,url,text); } } void printhref_raw(const char *url, const char *text, bool largewindow) { if (w_robot){ htmlprintf ("%s",url,text); }else{ const char *script = largewindow ? "popup_large" : "popup_small"; htmlprintf ("%s",script,url,text); } } string format_date (unsigned format, PARAM_STRING pdate) { static TRANS_NOTLOAD *tbmonth[]={P_MSG_U(I_JANUARY,"Jan"),P_MSG_U(I_FEBRUARY,"Feb"),P_MSG_U(I_MARCH,"Mar") ,P_MSG_U(I_APRIL,"Apr"),P_MSG_U(I_MAY,"May"),P_MSG_U(I_JUNE,"Jun") ,P_MSG_U(I_JULY,"Jul"),P_MSG_U(I_AUGUST,"Aug"),P_MSG_U(I_SEPTEMBER,"Sep") ,P_MSG_U(I_OCTOBER,"Oct"),P_MSG_U(I_NOVEMBER,"Nov"),P_MSG_U(I_DECEMBER,"Dec")}; const char *date = pdate.ptr; if (format != 0){ return date; }else if (strlen(date)==19){ int year = atoi(date); int month = atoi(date+5); int day = atoi(date+8); int hour = atoi(date+11); int minu = atoi(date+14); const char *ampm = "AM"; if (hour > 12){ hour -= 12; ampm="PM"; } return string_f (" %s %d %04d @%d:%02d%s",tbmonth[month-1]->get(),day,year,hour,minu,ampm); }else if (strlen(date)==10){ int year = atoi(date); int month = atoi(date+5); int day = atoi(date+8); return string_f (" %s %d %04d",tbmonth[month-1]->get(),day,year); }else{ return date; } } string format_time (unsigned format, PARAM_STRING ptime) { const char *time = ptime.ptr; if (format != 0){ return string(time,5); }else if (strlen(time)==8){ int hour = atoi(time); int minu = atoi(time+3); const char *ampm = "AM"; if (hour > 12){ hour -= 12; ampm="PM"; } return string_f ("%d:%02d%s",hour,minu,ampm); }else{ return time; } } void util_formanchor() { htmlprintf ("\n"); } void button_row(_F_button_row &c, int border, const char *bgcolor, bool alignleft) { vector left_lines; tlmpweb_pushgrab(left_lines); c.align="left"; c.draw(); tlmpweb_popgrab(); vector right_lines; tlmpweb_pushgrab(right_lines); c.align="right"; c.spliton = false; c.draw_right(); tlmpweb_popgrab(); if (left_lines.size() > 0 || right_lines.size()>0){ DIV cols; cols.dispflex().flowrow(); if (!alignleft) cols.sfloat("right"); cols.print(); if (c.href_arrow_left.size() > 0){ DIV col; col.flexfixe().print(); draw_left_arrow(c.arrow_left_visible,c.href_arrow_left); } { DIV col; col.flexgrow().overflow("hidden").print(); DIV d; d.dispflex().flowrow().bg("none").overflow("hidden").print(); if (left_lines.size() > 0){ htmlout (left_lines); htmlout ("\n"); } if (c.endline){ htmlout ("
\n"); unsigned height = draw_tab_height(); htmlprintf ("\n",c.endline_width,height); htmlprintf ("\n",height,c.endline_width); htmlout ("\n"); htmlout ("
\n"); } } if (c.href_arrow_right.size() > 0){ DIV col; col.flexfixe().print(); draw_right_arrow(c.arrow_right_visible,c.href_arrow_right); } DIV end; end.flexfixe().print(); if (right_lines.size() > 0){ htmlout (right_lines); htmlout ("\n"); } } } void button_row(_F_button_row &c, int border, const char *bgcolor) { button_row(c,border,bgcolor,true); } void button_row(_F_button_row &c, int border) { button_row (c,border,"white",true); } void button_row(_F_button_row &c) { button_row(c,0,"white",true); } void _F_button_row::draw_right() { } void _F_button_row::reset() { spliton = false; tlmpweb_resetgrab(); } void _F_button_row::split() { if (spliton) htmlout ("\n"); htmlout ("
\n"); spliton = true; } void _F_button_row::drawendline(unsigned width) { endline_width = width; endline = true; } void _F_button_row::drawleftarrow (PARAM_STRING href, bool visible) { arrow_left_visible = visible; href_arrow_left = href.ptr; } void _F_button_row::drawrightarrow (PARAM_STRING href, bool visible) { arrow_right_visible = visible; href_arrow_right = href.ptr; } static const char *tbhttptype[]={ "application/octet-stream", //FILE_UNKNOWN "text/html", //FILE_TEXT "audio/mp3", //FILE_SOUND_MP3 "audio/ogg", //FILE_SOUND_OGG "image/jpeg", //FILE_IMAGE_JPG "image/png", //FILE_IMAGE_PNG "image/gig", //FILE_IMAGE_GIF "video/mpeg", //FILE_VIDEO "application/octet-stream", //FILE_DOC_SUDOKU "application/octet-stream", //FILE_DOC_CHECKER "application/octet-stream", //FILE_DOC_CHESS "application/octet-stream", //FILE_DOC_TICTACTO "application/octet-stream", //FILE_ZIP "application/octet-stream", //FILE_TGZ "application/octet-stream", //FILE_DOC_WORDPROC "application/octet-stream", //FILE_DOC_WHITEBOARD "video/webm", //FILE_WEBM "image/tiff", //FILE_IMAGE_TIFF "application/pdf", //FILE_PDF "application/octet-stream", //FILE_DOC_CALC "application/octet-stream", //FILE_DOC_PHOTOS "application/octet-stream", //FILE_DOC_VIDCONF "audio/x-wav", // FILE_SOUND_WAV "video/x-msvideo", // FILE_AVI }; static_assert(sizeof(tbhttptype)/sizeof(tbhttptype[0])==FILE_TYPE_LAST,"tbhttptype is incomplete"); static void util_sendfile_common(CONNECT_INFO &con, const READINFO_receive &info, const BOB_TYPE &content, bool more, const char *handle, const char *session) { glocal CONNECT_INFO *con = &con; tlmpweb_setmodified(info.modified); tlmpweb_setexpire(time(NULL)+365*24*60*60); // Expires in one year tlmpweb_doctype (tbhttptype[info.file_type],info.size); htmlwrite (content.getbuffer(),content.getsize()); glocal bool more = more; while (glocal.more){ (*glocal.con,session,handle); htmlwrite (content.getbuffer(),content.getsize()); glocal.more = more; //tlmp_error ("readmore success=%d msg=%s\n",success,msg); } } static int util_sendfile (const char *fname) { int ret = -1; FILE *fin = fopen (fname,"r"); if (fin != NULL){ struct stat64 st; time_t mod = 0; unsigned size = 0; if (fstat64(fileno(fin),&st)!=-1){ size = st.st_size; mod = st.st_mtime; } tlmpweb_setmodified(mod); tlmpweb_doctype ("image/jpeg",size); char buf[64*1024]; int n; while ((n=fread(buf,1,sizeof(buf),fin))>0){ htmlwrite(buf,n); } fclose (fin); ret = 0; } return ret; } int util_sendfile (CONNECT_INFO &con, PARAM_STRING session, PARAM_STRING filename) { glocal int ret = -1; glocal CONNECT_INFO *con = &con; glocal const char *session = session.ptr; glocal bool is_mini_photo = strstr(filename.ptr,"/public/mini-photo.jpg")!=nullptr; glocal bool is_photo = strstr(filename.ptr,"/public/photo.jpg")!=nullptr; (con,session,filename,"",false); if (success){ glocal.ret = 0; util_sendfile_common(*glocal.con,info,content,more,handle,glocal.session); }else if (glocal.is_mini_photo){ glocal.ret = util_sendfile("/var/www/html/no-mini-photo.jpg"); }else if (glocal.is_photo){ glocal.ret = util_sendfile("/var/www/html/no-photo.jpg"); }else{ } return glocal.ret; } /* Send a file using the public api. The file is /username/file. We accept also username/file. So we extract the username */ int util_sendpublicfile (CONNECT_INFO &con, PARAM_STRING filename) { glocal int ret = -1; glocal con; const char *pt = filename.ptr; if (*pt == '/') pt++; const char *start = pt; pt = strchr(pt,'/'); if (pt != NULL){ glocal const char *filename = pt; glocal string username = string(start,pt-start); (con,glocal.username,pt,0); if (!success){ //tlmp_warning ("sendpublicfile :%s: :%s: %s\n",glocal.username.c_str(),glocal.filename,msg); if (strcmp(glocal.filename,"/project/photo.jpg")==0){ glocal.ret = util_sendfile ("/var/www/html/no-photo.jpg"); }else if (strcmp(glocal.filename,"/project/mini-photo.jpg")==0){ glocal.ret = util_sendfile ("/var/www/html/no-mini-photo.jpg"); } if (glocal.ret != 0) tlmp_warning ("Can't read public file %s for user %s: %s\n",glocal.filename,glocal.username.c_str(),msg); }else{ glocal.ret = 0; util_sendfile_common(glocal.con,info,content,more,handle,"public"); } } return glocal.ret; } string util_flipspaces(PARAM_STRING src) { string ret = src.ptr; for (auto &c:ret){ if (c == ' ') c = '_'; } return ret; } /* Extract a URL (if found) from a line. Return true if it was a URL. */ static bool util_is_url(const char *txt, const char *&end,string &url) { bool ret = false; const char *pt; if (is_start_any_ofnc(txt,pt,"http://","https://")){ pt = txt; while (*pt > ' ' && is_not_in(*pt,'"','>',',',')')) pt++; // A URL can't end with a period if (pt > txt && pt[-1] == '.') pt--; url = string(txt,pt-txt); end = pt; ret = true; } return ret; } /* Create a link to an internal part of the application using subformsubmit. This works inside content being itself clickable. */ string util_subformsubmit (PARAM_STRING link, PARAM_STRING text) { return string_f("%s\n" ,tlmpweb_curpage(),link.ptr,text.ptr); } /* Format and print a ... with openitab() */ void util_print_span(PARAM_STRING url) { htmlprintf ("%s\n",url.ptr,url.ptr); } /* Format a ... with openintab() */ static string util_span(PARAM_STRING url) { if (tlmpweb_ismobile()){ return string_f("%s",url.ptr,url.ptr); }else{ return string_f("%s",url.ptr,url.ptr,url.ptr); } } /* Works like util_span, but present the link as a button */ static string util_link_button(PARAM_STRING url, PARAM_STRING id) { if (id.ptr != nullptr && id.ptr[0] != '\0'){ return string_f ("
%s
\n" ,url.ptr,url.ptr,id.ptr,url.ptr,id.ptr ,MSG_U(I_READNEWS,"Read the news story")); }else{ return string_f ("
%s
\n" ,url.ptr,url.ptr,url.ptr ,MSG_R(I_READNEWS)); } } /* Format an open ... with openitab() (no closing */ static string util_open_span(PARAM_STRING url) { if (tlmpweb_ismobile()){ return string_f("",url.ptr); }else{ return string_f("",url.ptr,url.ptr); } } void util_clickable_img (PARAM_STRING url, const char *image_width, unsigned border) { htmlout ("",image_width,border,url.ptr); htmlout (""); } /* Create the HTML for a clickable image (using javascript openintab()) */ string util_clickable_img (PARAM_STRING url, unsigned image_width) { string ret; ret.reserve(500); ret = util_open_span(url); ret += string_f(""; return ret; } /* Remove the dot at the end of a string */ static void util_remove_dot(string &line, string &dot) { if (line.size() > 0){ dot.clear(); auto last = line.size()-1; if (line[last] == '.'){ // The line ends with a period. It is probably not part of the ID. line.resize(last); dot = "."; } } } #define UTF8_ONE_BYTE_MASK 0b10000000 #define UTF8_ONE_BYTE_COUNT 0 #define UTF8_TWO_BYTE_MASK 0b11100000 #define UTF8_TWO_BYTE_COUNT 0b11000000 #define UTF8_THREE_BYTE_MASK 0b11110000 #define UTF8_THREE_BYTE_COUNT 0b11100000 #define UTF8_FOUR_BYTE_MASK 0b11111000 #define UTF8_FOUR_BYTE_COUNT 0b11110000 // This one could use a better name, I just don't know a better one (yet?) #define UTF8_OTHER_MASK 0b00111111 static size_t utf8_codepoint_size(uint8_t text) { if((text & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_COUNT) { return 1; } if((text & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_COUNT) { return 2; } if((text & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_COUNT) { return 3; } return 4; } /* Copy a local link (user/project/document) Return the pointer */ static const char *util_copy_locallink(const char *txt, string &doc, string &dot) { const char *start = txt; while (*txt > ' ' && is_not_in(*txt,'"','\'','<','>',',','=','&')) txt++; doc = string(start,txt-start); util_remove_dot (doc,dot); return txt; } static const char *util_name_from_path(const string &doc) { const char *name = strrchr(doc.c_str(),'/'); if (name == nullptr){ name = doc.c_str(); }else{ name++; } return name; } // Present a video in a span static string util_span_video(const string &url, unsigned image_width) { string ret; ret += util_open_span(url); ret += string_f ("\n" ,image_width,url.c_str()); ret += ""; return ret; } // Validate that a document exist and has the proper type static void util_validate_doc ( CONNECT_INFO &con, const string &doc, string &errmsg, // Will contain the error message if any bool (*check)(FILE_TYPE file_type), const char *filenottype) // Error message { FILEINFO info; ENTRY_TYPE type = util_entrytype(con,string_f("/projects/%s",doc.c_str()),info); if (type == ENTRY_DIR){ errmsg = string_f(MSG_U(E_ISDIR,"Tag %s: %s is a folder\n"),"_IMG",doc.c_str()); }else if (is_not_in(type,ENTRY_FILE,ENTRY_DOCUMENT)){ errmsg = string_f(MSG_U(E_NOTFOUND,"Tag %s: document %s does not exist\n"),"_IMG",doc.c_str()); }else if (!check(info.file_type)){ errmsg = string_f(filenottype,doc.c_str()); } } static string util_iframe (const string &url) { string ret = util_open_span(url); ret += "
\n"; ret += string_f ("\n" ,url.c_str()); ret += "
"; return ret; } /* Format a short message, remove the signature Escapes < and > Supports ? for URL if validate is true, does some validation. Errors are reported in errmsg. Support the quoted reply mode. In this mode, the message is a reply to another message. The message contains part of the other message. The parts started with the greatest than (>) character. */ string util_format_shortmsg ( PARAM_STRING msg, PARAM_STRING msgid, unsigned nblines, size_t size, unsigned image_width, bool validate, CONNECT_INFO &con, string &errmsg, bool &truncated) // If true, the message was not all formatted/displayed { bool is_mobile = tlmpweb_ismobile(); const int para_indent = is_mobile ? 40 : 20; // Indentation for * and # sub-lists truncated = false; const char *txt = msg.ptr; string ret; ret.reserve(nblines > 0 ? nblines*100 : 1000); unsigned pos=0; unsigned noline=0; if (nblines == 0) nblines=(unsigned)-1; bool last_is_reply = false; // The last line (paragraph) started with a >, * or # bool is_reply_open = false; // Are we formatting a line starting with >, * or # const int max_indent=2; int item_number[max_indent+1] = {1,1,1}; // Item number for lines starting with # while (*txt != '\0' && noline < nblines){ if (*txt == '\n'){ if (is_reply_open){ ret += "
\n\n"; is_reply_open = false; }else{ ret += "
\n"; } pos=0; noline++; }else{ if (pos == 0){ static const char *pad = "
\n"; if (is_any_of (*txt,'>','*','#','=')){ if (!last_is_reply) ret += pad; last_is_reply = true; is_reply_open = true; ret += "
\n"; if (*txt == '>'){ ret += "
"; while (*txt == '>'){ ret += ">"; txt++; } }else if (is_any_of(*txt,'*','#')){ char first = *txt; int nb = -1; while (*txt == first){ nb++; txt++; } if (nb > max_indent) nb = max_indent; ret += string_f("
",5+nb*para_indent); if (first == '*'){ static const char *tbht[]={"
","
","
"}; ret += tbht[nb]; }else{ ret += string_f("%d.",item_number[nb]); item_number[nb]++; for (int i=nb+1; i max_indent+2) nb = max_indent+2; ret += string_f("
",5+nb*para_indent); if (is_any_of(*txt,'>','^','<','!','-','#','*')){ static const map tb={ {'>',"▸"}, // Triangle pointing right {'<',"◂"}, // Triangle pointing left {'^',"▴"}, // Triangle pointing up {'!',"▾"}, // Triangle pointing down {'-',"–"}, // en dash {'#',"◆"}, // Black diamond {'*',"●"}, // Black circle }; auto it = tb.find(*txt); if (it != tb.end()) ret += it->second; txt++; }else if (txt[0] == '&' && txt[1] == '#' && isdigit(txt[2]) && isdigit(txt[3]) && isdigit(txt[4]) && isdigit(txt[5]) && txt[6] == ';'){ // Accept any HTML decimal symbol &#NNNN; maybe useful. Harder to use ret += string_view(txt,7); txt += 7; }else{ // Put a white space the same size as the symbols above ret += ""; } } ret += "
\n
\n"; }else{ if (last_is_reply) ret += pad; last_is_reply = false; for (int i=0; i","","","","","","","","","","")){ while (txt < pt) ret += *txt++; txt--; // There is a txt++ at the end of the loop. }else{ ret += "<"; } }else if (*txt == '>'){ ret += ">"; }else if (*txt == '\t'){ unsigned nb = 4 - (pos%4); for (unsigned i=0; i%s\n" ,tlmpweb_curpage(),step_doctopic,tmp.c_str(),MSG_U(I_LINK,"link")); txt--; }else if (is_start_any_of(txt,pt,"_DOC=")){ txt = pt; string doc,dot; txt = util_copy_locallink(txt,doc,dot); if (doc.size() == 0){ ret += "_DOC="; if (validate){ errmsg = MSG_U(E_NODOCSPECIFIED,"Tag _DOC, no document specified"); } }else{ const char *name = util_name_from_path(doc); ret += string_f("%s\n" ,tlmpweb_curpage(),step_projects,doc.c_str(),name,doc.c_str()); ret += dot; if (validate){ FILEINFO info; ENTRY_TYPE type = util_entrytype(con,string_f("/projects/%s",doc.c_str()),info); if (type == ENTRY_DIR){ errmsg = string_f(MSG_R(E_ISDIR),"_DOC",doc.c_str()); }else if (is_not_in(type,ENTRY_FILE,ENTRY_DOCUMENT)){ errmsg = string_f(MSG_R(E_NOTFOUND),"_DOC",doc.c_str()); } } } txt--; }else if (is_start_any_of(txt,pt,"_USER="," @") || (*txt == '@' && pos == 0)){ // @ inside a line, @ at the start of a line (pos == 0) const char *start_txt = txt; if (pos == 0 && *txt == '@') pt = txt+1; txt = pt; while (*txt > ' ' && is_not_in(*txt,'"','>',',')) txt++; string user(pt,txt-pt); string dot; util_remove_dot (user,dot); if (user.size() == 0){ ret += string(start_txt,txt-start_txt); }else{ ret += " "; if (user == "admin"){ // admin is not in the contact list of everyone, but has a public page ret += ""; }else{ string dir = string_f("/projects/%s/public",user.c_str()); unsigned mini_image_width = is_mobile ? 60 : 40; ret += util_mini_img(step_image,mini_image_width,dir,"mini-photo.jpg",""); } string add = string_f("webstep=%d&webtab_add=1:%s:inbox~%s&recipients2=%s" ,step_talks,userinfo.name.c_str(),MSG_R(I_SHORTINBOX),user.c_str()); ret += " "; ret += util_subformsubmit(add,user); ret += dot; } txt--; }else{ size_t sizecar = utf8_codepoint_size(*txt); while (sizecar > 0){ ret += *txt++; sizecar--; } txt--; pos++; } } txt++; } if (is_reply_open) ret += "
\n
\n"; if (*txt != '\0' || (size != 0 && size != strlen(msg.ptr))){ truncated = true; } return ret; } string util_format_shortmsg ( PARAM_STRING msg, unsigned nblines, size_t size, unsigned image_width, bool validate, CONNECT_INFO &con, string &errmsg) { bool truncated; PARAM_STRING msgid(""); return util_format_shortmsg (msg,msgid,nblines,size,image_width,validate,con,errmsg,truncated); } string util_format_shortmsg (PARAM_STRING msg, PARAM_STRING msgid, unsigned nblines, size_t size, unsigned image_width, bool &truncated) { string errmsg; CONNECT_INFO con; return util_format_shortmsg (msg,msgid,nblines,size,image_width,false,con,errmsg,truncated); } string util_format_shortmsg (PARAM_STRING msg, unsigned nblines, size_t size, unsigned image_width) { bool truncated; return util_format_shortmsg (msg,"",nblines,size,image_width,truncated); } string util_format_shortmsg (PARAM_STRING txt, unsigned image_width) { bool truncated; return util_format_shortmsg (txt,"",0,0,image_width,truncated); } // Send an HTML document. Does some remaping of relative URLs. Prevent scripting as well. void _F_public_page::sendhtml (const BOB_TYPE &content) { string tmp((const char*)content.getbuffer(),content.getsize()); const char *txt = tmp.c_str(); const char *tosend = txt; while (*txt != '\0'){ if (*txt == '<'){ // Check if this is