source: git/src/cavernlog.cc @ a91c2b0

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since a91c2b0 was a91c2b0, checked in by Olly Betts <olly@…>, 11 years ago

src/cavernlog.cc: Remove useless extra quoting when invoking vim.

  • 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.