source: git/src/cavernlog.cc @ 414cdd0

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-datawalls-data-hanging-as-warning
Last change on this file since 414cdd0 was e02a6a6, checked in by Olly Betts <olly@…>, 14 years ago

configure.in,src/: Define GETC() in config.h and use it everywhere.
Similarly define PUTC() to use putc_unlocked() where available and
use that everywhere too.

git-svn-id: file:///home/survex-svn/survex/trunk@3550 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

  • Property mode set to 100644
File size: 9.2 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005,2006,2010 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, 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(0, 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, 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#ifdef __WXMSW__
107        wxString cmd = wxT("notepad $f");
108#else
109        wxString cmd = wxT("x-terminal-emulator -title $t -e vim -c $l $f");
110        // wxString cmd = "x-terminal-emulator -title $t -e emacs +$l $f";
111        // wxString cmd = "x-terminal-emulator -title $t -e nano +$l $f";
112        // wxString cmd = "x-terminal-emulator -title $t -e jed -g $l $f";
113#endif
114        const char * p = getenv("SURVEXEDITOR");
115        if (p) {
116            cmd = wxString(p, wxConvUTF8);
117            if (!cmd.find(wxT("$f"))) {
118                cmd += wxT(" $f");
119            }
120        }
121        size_t i = 0;
122        while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
123            if (++i >= cmd.size()) break;
124            switch (cmd[i]) {
125                case wxT('$'):
126                    cmd.erase(i, 1);
127                    break;
128                case wxT('f'): {
129                    wxString f = escape_for_shell(href.substr(0, colon), true);
130                    cmd.replace(i - 1, 2, f);
131                    i += f.size() - 1;
132                    break;
133                }
134                case wxT('t'): {
135                    wxString t = escape_for_shell(title);
136                    cmd.replace(i - 1, 2, t);
137                    i += t.size() - 1;
138                    break;
139                }
140                case wxT('l'): {
141                    wxString l = escape_for_shell(href.substr(colon + 1));
142                    cmd.replace(i - 1, 2, l);
143                    i += l.size() - 1;
144                    break;
145                }
146                default:
147                    ++i;
148            }
149        }
150#ifdef __WXMSW__
151        _wsystem(cmd.c_str());
152#else
153        system(cmd.mb_str());
154#endif
155    }
156}
157
158int
159CavernLogWindow::process(const wxString &file)
160{
161    SetFocus();
162    filename = file;
163
164#ifdef __WXMSW__
165    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
166#else
167    setenv("SURVEX_UTF8", "1", 1);
168#endif
169
170    wxString escaped_file = escape_for_shell(file, true);
171#ifdef __WXMSW__
172    wxString cmd;
173    {
174        wchar_t * argv0 = _wpgmptr;
175        wchar_t * slash = wcsrchr(argv0, L'\\');
176        if (slash) {
177            cmd.assign(argv0, slash - argv0 + 1);
178        }
179    }
180    cmd += L"cavern";
181    cmd = escape_for_shell(cmd, false);
182#else
183    char *cavern = use_path(msg_exepth(), "cavern");
184    wxString cmd = escape_for_shell(wxString(cavern, wxConvUTF8), false);
185    osfree(cavern);
186#endif
187    cmd += wxT(" -o ");
188    cmd += escaped_file;
189    cmd += wxT(' ');
190    cmd += escaped_file;
191
192#ifdef __WXMSW__
193    FILE * cavern_out = _wpopen(cmd.c_str(), L"r");
194#else
195    FILE * cavern_out = popen(cmd.mb_str(), "r");
196#endif
197    if (!cavern_out) {
198        wxString m;
199        m.Printf(wmsg(/*Couldn't open pipe: `%s'*/17), cmd.c_str());
200        m += wxT(" (");
201        m += wxString(strerror(errno), wxConvUTF8);
202        m += wxT(')');
203        wxGetApp().ReportError(m);
204        return -2;
205    }
206
207    int cavern_fd;
208#ifdef __WXMSW__
209    cavern_fd = _fileno(cavern_out);
210#else
211    cavern_fd = fileno(cavern_out);
212#endif
213    assert(cavern_fd < FD_SETSIZE); // FIXME we shouldn't just assert, but what else to do?
214    wxString cur;
215    int link_count = 0;
216    // We're only guaranteed one character of pushback by ungetc() but we
217    // need two so for portability we implement the second ourselves.
218    int left = EOF;
219    while (!feof(cavern_out)) {
220        fd_set rfds, efds;
221        FD_ZERO(&rfds);
222        FD_SET(cavern_fd, &rfds);
223        FD_ZERO(&efds);
224        FD_SET(cavern_fd, &efds);
225        // Timeout instantly.
226        struct timeval timeout;
227        timeout.tv_sec = 0;
228        timeout.tv_usec = 0;
229        if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
230            Update();
231            FD_SET(cavern_fd, &rfds);
232            FD_SET(cavern_fd, &efds);
233            // Set timeout to 0.1 seconds.
234            timeout.tv_sec = 0;
235            timeout.tv_usec = 100000;
236            if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
237                wxYield();
238                continue;
239            }
240        }
241        if (!FD_ISSET(cavern_fd, &rfds)) {
242            // Error, which pclose() should report.
243            break;
244        }
245        int ch;
246        if (left == EOF) {
247            ch = GETC(cavern_out);
248            if (ch == EOF) break;
249        } else {
250            ch = left;
251            left = EOF;
252        }
253        // Decode UTF-8 first to avoid security issues with <, >, &, etc
254        // encoded using multibyte encodings.
255        if (ch >= 0xc0 && ch < 0xf0) {
256            int ch1 = GETC(cavern_out);
257            if ((ch1 & 0xc0) != 0x80) {
258                left = ch1;
259            } else if (ch < 0xe0) {
260                /* 2 byte sequence */
261                ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
262            } else {
263                /* 3 byte sequence */
264                int ch2 = GETC(cavern_out);
265                if ((ch2 & 0xc0) != 0x80) {
266                    ungetc(ch2, cavern_out);
267                    left = ch1;
268                } else {
269                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6) | (ch2 & 0x3f);
270                }
271            }
272        }
273
274        switch (ch) {
275            case '\r':
276                // Ignore.
277                break;
278            case '\n': {
279                if (cur.empty()) continue;
280#ifndef __WXMSW__
281                size_t colon = cur.find(':');
282#else
283                // If the path is "C:\path\to\file.svx" then don't split at the
284                // : after the drive letter!  FIXME: better to look for ": "?
285                size_t colon = cur.find(':', 2);
286#endif
287                if (colon != wxString::npos && colon < cur.size() - 1) {
288                    ++colon;
289                    size_t i = colon;
290                    while (i < cur.size() - 1 &&
291                           cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
292                        ++i;
293                    }
294                    if (i > colon && cur[i] == wxT(':') ) {
295                        colon = i;
296                        wxString tag = wxT("<a href=\"");
297                        tag.append(cur, 0, colon);
298                        while (cur[++i] == wxT(' ')) { }
299                        tag += wxT("\" target=\"");
300                        tag.append(cur, i, wxString::npos);
301                        tag += wxT("\">");
302                        cur.insert(0, tag);
303                        cur.insert(colon + tag.size(), wxT("</a>"));
304                        ++link_count;
305                    }
306                }
307
308                // Save the scrollbar positions.
309                int scroll_x = 0, scroll_y = 0;
310                GetViewStart(&scroll_x, &scroll_y);
311
312                cur += wxT("<br>\n");
313                AppendToPage(cur);
314
315                if (!link_count) {
316                    // Auto-scroll the window until we've reported a warning or
317                    // error.
318                    int x, y;
319                    GetVirtualSize(&x, &y);
320                    int xs, ys;
321                    GetClientSize(&xs, &ys);
322                    y -= ys;
323                    int xu, yu;
324                    GetScrollPixelsPerUnit(&xu, &yu);
325                    Scroll(scroll_x, y / yu);
326                } else {
327                    // Restore the scrollbar positions.
328                    Scroll(scroll_x, scroll_y);
329                }
330
331                cur.clear();
332                break;
333            }
334            case '<':
335                cur += wxT("&lt;");
336                break;
337            case '>':
338                cur += wxT("&gt;");
339                break;
340            case '&':
341                cur += wxT("&amp;");
342                break;
343            case '"':
344                cur += wxT("&#22;");
345                continue;
346            default:
347                if (ch >= 128) {
348                    cur += wxString::Format(wxT("&#%u;"), ch);
349                } else {
350                    cur += (char)ch;
351                }
352        }
353    }
354
355    int retval = pclose(cavern_out);
356    if (retval) {
357        AppendToPage(wxT("<avenbutton default id=1234 name=\"Reprocess\">"));
358        if (retval == -1) {
359            wxString m = wxT("Problem running cavern: ");
360            m += wxString(strerror(errno), wxConvUTF8);
361            wxGetApp().ReportError(m);
362            return -2;
363        }
364        return -1;
365    }
366    if (link_count) {
367        AppendToPage(wxT("<avenbutton id=1234 name=\"Reprocess\">"));
368        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
369        Update();
370    }
371    return link_count;
372}
373
374void
375CavernLogWindow::OnReprocess(wxCommandEvent & e)
376{
377    SetPage(wxString());
378    if (process(filename) == 0) {
379        OnOK(e);
380    }
381}
382
383void
384CavernLogWindow::OnOK(wxCommandEvent &)
385{
386    mainfrm->InitialiseAfterLoad(filename);
387}
Note: See TracBrowser for help on using the repository browser.