source: git/src/cavernlog.cc @ e27a0c3

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernlogstereowalls-datawalls-data-hanging-as-warning
Last change on this file since e27a0c3 was da96015, checked in by Olly Betts <olly@…>, 11 years ago

src/,tests/: Report column numbers as well as line numbers for some
cavern errors and warnings, and update aven's cavern log parser to
handle these. Simplify handling of quantity lists to only recognise
'DEFAULT' as the first item. Add more testcases, expand some
existing testcases, and expected output for more.

  • Property mode set to 100644
File size: 10.3 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005,2006,2010,2011,2012 Olly Betts
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
21#ifdef HAVE_CONFIG_H
22# include <config.h>
23#endif
24
25#include "aven.h"
26#include "cavernlog.h"
27#include "filename.h"
28#include "mainfrm.h"
29#include "message.h"
30
31#include <errno.h>
32#include <stdio.h>
33#include <stdlib.h>
34
35// For select():
36#ifdef HAVE_SYS_SELECT_H
37#include <sys/select.h>
38#endif
39#include <sys/time.h>
40#include <sys/types.h>
41#include <unistd.h>
42
43enum { LOG_REPROCESS = 1234 };
44
45BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
46    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
47    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
48END_EVENT_TABLE()
49
50static wxString escape_for_shell(wxString s, bool protect_dash = false)
51{
52    size_t p = 0;
53#ifdef __WXMSW__
54    bool needs_quotes = false;
55    while (p < s.size()) {
56        wxChar ch = s[p];
57        if (ch < 127) {
58            if (ch == wxT('"')) {
59                s.insert(p, 1, wxT('\\'));
60                ++p;
61                needs_quotes = true;
62            } else if (strchr(" <>&|^", ch)) {
63                needs_quotes = true;
64            }
65        }
66        ++p;
67    }
68    if (needs_quotes) {
69        s.insert(0u, 1, wxT('"'));
70        s += wxT('"');
71    }
72#else
73    if (protect_dash && !s.empty() && s[0u] == '-') {
74        // If the filename starts with a '-', protect it from being
75        // treated as an option by prepending "./".
76        s.insert(0, wxT("./"));
77        p = 2;
78    }
79    while (p < s.size()) {
80        // Exclude a few safe characters which are common in filenames
81        if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
82            s.insert(p, 1, wxT('\\'));
83            ++p;
84        }
85        ++p;
86    }
87#endif
88    return s;
89}
90
91CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, wxWindow * parent)
92    : wxHtmlWindow(parent), mainfrm(mainfrm_)
93{
94    int fsize = parent->GetFont().GetPointSize();
95    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
96    SetFonts(wxString(), wxString(), sizes);
97}
98
99void
100CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
101{
102    wxString href = link.GetHref();
103    wxString title = link.GetTarget();
104    size_t colon = href.rfind(wxT(':'));
105    if (colon != wxString::npos) {
106        size_t colon2 = href.rfind(wxT(':'), colon - 1);
107        if (colon2 != wxString::npos) swap(colon, colon2);
108#ifdef __WXMSW__
109        wxString cmd = wxT("notepad $f");
110#elif defined __WXMAC__
111        wxString cmd = wxT("open -t $f");
112#else
113        wxString cmd = wxT("x-terminal-emulator -title $t -e vim +'call cursor('$l,$c')' $f");
114        // wxString cmd = wxT("gedit -b $f +$l:$c $f");
115        // wxString cmd = wxT("x-terminal-emulator -title $t -e emacs +$l $f");
116        // wxString cmd = wxT("x-terminal-emulator -title $t -e nano +$l $f");
117        // wxString cmd = wxT("x-terminal-emulator -title $t -e jed -g $l $f");
118#endif
119        const char * p = getenv("SURVEXEDITOR");
120        if (p) {
121            cmd = wxString(p, wxConvUTF8);
122            if (!cmd.find(wxT("$f"))) {
123                cmd += wxT(" $f");
124            }
125        }
126        size_t i = 0;
127        while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
128            if (++i >= cmd.size()) break;
129            switch ((int)cmd[i]) {
130                case wxT('$'):
131                    cmd.erase(i, 1);
132                    break;
133                case wxT('f'): {
134                    wxString f = escape_for_shell(href.substr(0, colon), true);
135                    cmd.replace(i - 1, 2, f);
136                    i += f.size() - 1;
137                    break;
138                }
139                case wxT('t'): {
140                    wxString t = escape_for_shell(title);
141                    cmd.replace(i - 1, 2, t);
142                    i += t.size() - 1;
143                    break;
144                }
145                case wxT('l'): {
146                    wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
147                    cmd.replace(i - 1, 2, l);
148                    i += l.size() - 1;
149                    break;
150                }
151                case wxT('c'): {
152                    wxString l;
153                    if (colon2 == wxString::npos)
154                        l = wxT("0");
155                    else
156                        l = escape_for_shell(href.substr(colon2 + 1));
157                    cmd.replace(i - 1, 2, l);
158                    i += l.size() - 1;
159                    break;
160                }
161                default:
162                    ++i;
163            }
164        }
165#ifdef __WXMSW__
166        _wsystem(cmd.c_str());
167#else
168        system(cmd.mb_str());
169#endif
170    }
171}
172
173int
174CavernLogWindow::process(const wxString &file)
175{
176    SetFocus();
177    filename = file;
178
179#ifdef __WXMSW__
180    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
181#else
182    setenv("SURVEX_UTF8", "1", 1);
183#endif
184
185    wxString escaped_file = escape_for_shell(file, true);
186#ifdef __WXMSW__
187    wxString cmd;
188    {
189        DWORD len = 256;
190        wchar_t *buf = NULL;
191        while (1) {
192            DWORD got;
193            buf = (wchar_t*)osrealloc(buf, len * 2);
194            got = GetModuleFileNameW(NULL, buf, len);
195            if (got < len) break;
196            len += len;
197        }
198        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
199        if (wcsncmp(buf, L"\\\\?\\", 4) == 0) buf += 4;
200        wchar_t * slash = wcsrchr(buf, L'\\');
201        if (slash) {
202            cmd.assign(buf, slash - buf + 1);
203        }
204    }
205    cmd += L"cavern";
206    cmd = escape_for_shell(cmd, false);
207#else
208    char *cavern = use_path(msg_exepth(), "cavern");
209    wxString cmd = escape_for_shell(wxString(cavern, wxConvUTF8), false);
210    osfree(cavern);
211#endif
212    cmd += wxT(" -o ");
213    cmd += escaped_file;
214    cmd += wxT(' ');
215    cmd += escaped_file;
216
217#ifdef __WXMSW__
218    FILE * cavern_out = _wpopen(cmd.c_str(), L"r");
219#else
220    FILE * cavern_out = popen(cmd.mb_str(), "r");
221#endif
222    if (!cavern_out) {
223        wxString m;
224        m.Printf(wmsg(/*Couldn’t open pipe: “%s”*/17), cmd.c_str());
225        m += wxT(" (");
226        m += wxString(strerror(errno), wxConvUTF8);
227        m += wxT(')');
228        wxGetApp().ReportError(m);
229        return -2;
230    }
231
232    int cavern_fd;
233#ifdef __WXMSW__
234    cavern_fd = _fileno(cavern_out);
235#else
236    cavern_fd = fileno(cavern_out);
237#endif
238    assert(cavern_fd < FD_SETSIZE); // FIXME we shouldn't just assert, but what else to do?
239    wxString cur;
240    int link_count = 0;
241    // We're only guaranteed one character of pushback by ungetc() but we
242    // need two so for portability we implement the second ourselves.
243    int left = EOF;
244    while (!feof(cavern_out)) {
245        fd_set rfds, efds;
246        FD_ZERO(&rfds);
247        FD_SET(cavern_fd, &rfds);
248        FD_ZERO(&efds);
249        FD_SET(cavern_fd, &efds);
250        // Timeout instantly.
251        struct timeval timeout;
252        timeout.tv_sec = 0;
253        timeout.tv_usec = 0;
254        if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
255            Update();
256            FD_SET(cavern_fd, &rfds);
257            FD_SET(cavern_fd, &efds);
258            // Set timeout to 0.1 seconds.
259            timeout.tv_sec = 0;
260            timeout.tv_usec = 100000;
261            if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
262                wxYield();
263                continue;
264            }
265        }
266        if (!FD_ISSET(cavern_fd, &rfds)) {
267            // Error, which pclose() should report.
268            break;
269        }
270        int ch;
271        if (left == EOF) {
272            ch = GETC(cavern_out);
273            if (ch == EOF) break;
274        } else {
275            ch = left;
276            left = EOF;
277        }
278        // Decode UTF-8 first to avoid security issues with <, >, &, etc
279        // encoded using multibyte encodings.
280        if (ch >= 0xc0 && ch < 0xf0) {
281            int ch1 = GETC(cavern_out);
282            if ((ch1 & 0xc0) != 0x80) {
283                left = ch1;
284            } else if (ch < 0xe0) {
285                /* 2 byte sequence */
286                ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
287            } else {
288                /* 3 byte sequence */
289                int ch2 = GETC(cavern_out);
290                if ((ch2 & 0xc0) != 0x80) {
291                    ungetc(ch2, cavern_out);
292                    left = ch1;
293                } else {
294                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6) | (ch2 & 0x3f);
295                }
296            }
297        }
298
299        switch (ch) {
300            case '\r':
301                // Ignore.
302                break;
303            case '\n': {
304                if (cur.empty()) continue;
305#ifndef __WXMSW__
306                size_t colon = cur.find(':');
307#else
308                // If the path is "C:\path\to\file.svx" then don't split at the
309                // : after the drive letter!  FIXME: better to look for ": "?
310                size_t colon = cur.find(':', 2);
311#endif
312                if (colon != wxString::npos && colon < cur.size() - 2) {
313                    ++colon;
314                    size_t i = colon;
315                    while (i < cur.size() - 2 &&
316                           cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
317                        ++i;
318                    }
319                    if (i > colon && cur[i] == wxT(':') ) {
320                        colon = i;
321                        // Check for column number.
322                        while (++i < cur.size() - 2 &&
323                           cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
324                        if (i > colon + 1 && cur[i] == wxT(':') ) {
325                            colon = i;
326                        }
327                        wxString tag = wxT("<a href=\"");
328                        tag.append(cur, 0, colon);
329                        while (cur[++i] == wxT(' ')) { }
330                        tag += wxT("\" target=\"");
331                        tag.append(cur, i, wxString::npos);
332                        tag += wxT("\">");
333                        cur.insert(0, tag);
334                        cur.insert(colon + tag.size(), wxT("</a>"));
335                        ++link_count;
336                    }
337                }
338
339                // Save the scrollbar positions.
340                int scroll_x = 0, scroll_y = 0;
341                GetViewStart(&scroll_x, &scroll_y);
342
343                cur += wxT("<br>\n");
344                AppendToPage(cur);
345
346                if (!link_count) {
347                    // Auto-scroll the window until we've reported a warning or
348                    // error.
349                    int x, y;
350                    GetVirtualSize(&x, &y);
351                    int xs, ys;
352                    GetClientSize(&xs, &ys);
353                    y -= ys;
354                    int xu, yu;
355                    GetScrollPixelsPerUnit(&xu, &yu);
356                    Scroll(scroll_x, y / yu);
357                } else {
358                    // Restore the scrollbar positions.
359                    Scroll(scroll_x, scroll_y);
360                }
361
362                cur.clear();
363                break;
364            }
365            case '<':
366                cur += wxT("&lt;");
367                break;
368            case '>':
369                cur += wxT("&gt;");
370                break;
371            case '&':
372                cur += wxT("&amp;");
373                break;
374            case '"':
375                cur += wxT("&#22;");
376                continue;
377            default:
378                if (ch >= 128) {
379                    cur += wxString::Format(wxT("&#%u;"), ch);
380                } else {
381                    cur += (char)ch;
382                }
383        }
384    }
385
386    int retval = pclose(cavern_out);
387    if (retval) {
388        AppendToPage(wxString::Format(wxT("<avenbutton default id=1234 name=\"%s\">"),
389                                      wmsg(/*Reprocess*/184).c_str()));
390        if (retval == -1) {
391            wxString m = wxT("Problem running cavern: ");
392            m += wxString(strerror(errno), wxConvUTF8);
393            wxGetApp().ReportError(m);
394            return -2;
395        }
396        return -1;
397    }
398    if (link_count) {
399        AppendToPage(wxString::Format(wxT("<avenbutton id=1234 name=\"%s\">"),
400                                      wmsg(/*Reprocess*/184).c_str()));
401        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
402        Update();
403    }
404    return link_count;
405}
406
407void
408CavernLogWindow::OnReprocess(wxCommandEvent & e)
409{
410    SetPage(wxString());
411    if (process(filename) == 0) {
412        OnOK(e);
413    }
414}
415
416void
417CavernLogWindow::OnOK(wxCommandEvent &)
418{
419    mainfrm->InitialiseAfterLoad(filename);
420}
Note: See TracBrowser for help on using the repository browser.