This file is indexed.

/usr/bin/apport-cli is in apport 2.0.1-0ubuntu17.15.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#!/usr/bin/python

'''Command line Apport user interface.'''

# Copyright (C) 2007 - 2009 Canonical Ltd.
# Author: Michael Hofmann <mh21@piware.de>
#
# This program 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 2 of the License, or (at your
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

# Web browser support:
#    w3m, lynx: do not work
#    elinks: works

import os.path, os, sys, subprocess, re, errno
import tty, termios, tempfile
from datetime import datetime

from apport import unicode_gettext as _
import apport.ui

class CLIDialog:
    '''Command line dialog wrapper.'''

    def __init__(self, heading, text):
        self.heading = '\n*** ' + heading + '\n'
        self.text = text
        self.keys = []
        self.buttons = []
        self.visible = False

    def raw_input_char(self, prompt):
        '''raw_input, but read only one character'''

        sys.stdout.write(prompt)
        sys.stdout.write(' ')

        file = sys.stdin.fileno()
        saved_attributes = termios.tcgetattr(file)
        attributes = termios.tcgetattr(file)
        attributes[3] = attributes[3] & ~(termios.ICANON)
        attributes[6][termios.VMIN] = 1
        attributes[6][termios.VTIME] = 0
        termios.tcsetattr(file, termios.TCSANOW, attributes)

        try:
            ch = str(sys.stdin.read(1))
        finally:
            termios.tcsetattr(file, termios.TCSANOW, saved_attributes)

        sys.stdout.write('\n')
        return ch

    def show(self):
        self.visible = True
        print(self.heading)
        if self.text:
            print(self.text)

    def run(self, prompt=None):
        if not self.visible:
            self.show()

        sys.stdout.write('\n')
        try:
            # Only one button
            if len (self.keys) <= 1:
                self.raw_input_char(_('Press any key to continue...'))
                return 0
            # Multiple choices
            while True:
                if prompt is not None:
                    print(prompt)
                else:
                    print(_('What would you like to do? Your options are:'))
                for index, button in enumerate(self.buttons):
                    print('  %s: %s' % (self.keys[index], button))

                response = self.raw_input_char(_('Please choose (%s):') % ('/'.join(self.keys)))
                try:
                    return self.keys.index(response[0].upper()) + 1
                except ValueError:
                    pass
        except KeyboardInterrupt:
            sys.stdout.write('\n')
            sys.exit(1)

    def addbutton(self, button, hotkey=None):
        if hotkey:
            self.keys.append(hotkey)
            self.buttons.append(button)
        else:
            self.keys.append(re.search('&(.)', button).group(1).upper())
            self.buttons.append(re.sub('&', '', button))
        return len(self.keys)


class CLIProgressDialog(CLIDialog):
    '''Command line progress dialog wrapper.'''

    def __init__(self, heading, text):
        CLIDialog.__init__(self, heading, text)
        self.progresscount = 0

    def set(self, progress = None):
        self.progresscount = (self.progresscount + 1) % 5
        if self.progresscount:
            return

        if progress != None:
            sys.stdout.write('\r%u%%' % (progress * 100))
        else:
            sys.stdout.write('.')
        sys.stdout.flush()

class CLIUserInterface(apport.ui.UserInterface):
    '''Command line Apport user interface'''

    def __init__(self):
        apport.ui.UserInterface.__init__(self)
        self.in_update_view = False

    def _get_details(self):
        '''Build report string for display.'''

        details = u''
        max_show = 1000000
        for key in sorted(self.report):
            details += u'== %s =================================\n' % key
            # string value
            keylen = len(self.report[key])
            if not hasattr(self.report[key], 'gzipvalue') and \
                hasattr(self.report[key], 'isspace') and \
                not self.report._is_binary(self.report[key]) and \
                keylen < max_show:
                l = self.report[key]
            elif keylen >= max_show:
                l = _('(%i bytes)') % keylen
            else:
                l = _('(binary data)')

            if type(l) == type(b''):
                l = l.decode('UTF-8', errors='ignore')
            details += l
            details += u'\n\n'

        return details


    def ui_update_view(self):
        self.in_update_view = True
        report = self._get_details()
        try:
            subprocess.Popen(['/usr/bin/sensible-pager'],
                    stdin=subprocess.PIPE,
                    close_fds=True).communicate(report.encode('UTF-8'))
        except IOError as e:
            # ignore broken pipe (premature quit)
            if e.errno == errno.EPIPE:
                pass
            else:
                raise
        self.in_update_view = False

    #
    # ui_* implementation of abstract UserInterface classes
    #

    def ui_present_report_details(self, allowed_to_report=True):
        dialog = CLIDialog (_('Send problem report to the developers?'),
                _('After the problem report has been sent, please fill out the form in the\n'
                  'automatically opened web browser.'))

        complete = dialog.addbutton(_('&Send report (%s)') %
                self.format_filesize(self.get_complete_size()))

        if self.can_examine_locally():
            examine = dialog.addbutton(_('&Examine locally'))
        else:
            examine = None

        view = dialog.addbutton(_('&View report'))
        save = dialog.addbutton(_('&Keep report file for sending later or copying to somewhere else'))
        ignore = dialog.addbutton(_('Cancel and &ignore future crashes of this program version'))

        dialog.addbutton(_('&Cancel'))

        import time
        while True:
            response = dialog.run()

            return_value = { 'restart' : False, 'blacklist' : False,
                             'report' : False, 'examine' : False }
            if response == examine:
                return_value['examine'] = True
                return return_value
            elif response == complete:
                return_value['report'] = True
            elif response == ignore:
                return_value['blacklist'] = True
            elif response == view:
                self.collect_info()
                self.ui_update_view()
                continue
            elif response == save:
                # we do not already have a report file if we report a bug
                if not self.report_file:
                    prefix = 'apport.'
                    if 'Package' in self.report:
                        prefix += self.report['Package'].split()[0] + '.'
                    (fd, self.report_file) = tempfile.mkstemp(prefix=prefix, suffix='.apport')
                    self.report.write(os.fdopen(fd, 'w'))

                print(_('Problem report file:') + ' ' + self.report_file)

            return return_value

    def ui_info_message(self, title, text):
        dialog = CLIDialog(title, text)
        dialog.addbutton(_('&Confirm'))
        dialog.run()

    def ui_error_message(self, title, text):
        dialog = CLIDialog(_('Error: %s') % title, text)
        dialog.addbutton(_('&Confirm'))
        dialog.run()

    def ui_start_info_collection_progress(self):
        self.progress = CLIProgressDialog (
                _('Collecting problem information'),
                _('The collected information can be sent to the developers to improve the\n'
                  'application. This might take a few minutes.'))
        self.progress.show()

    def ui_pulse_info_collection_progress(self):
        self.progress.set()

    def ui_stop_info_collection_progress(self):
        sys.stdout.write('\n')

    def ui_start_upload_progress(self):
        self.progress = CLIProgressDialog (
                _('Uploading problem information'),
                _('The collected information is being sent to the bug tracking system.\n'
                  'This might take a few minutes.'))
        self.progress.show()

    def ui_set_upload_progress(self, progress):
        self.progress.set(progress)

    def ui_stop_upload_progress(self):
        sys.stdout.write('\n')

    def ui_question_yesno(self, text):
        '''Show a yes/no question.

        Return True if the user selected "Yes", False if selected "No" or
        "None" on cancel/dialog closing.
        '''
        dialog = CLIDialog(text, None)
        r_yes = dialog.addbutton('&Yes')
        r_no = dialog.addbutton('&No')
        r_cancel = dialog.addbutton(_('&Cancel'))
        result = dialog.run()
        if result == r_yes:
            return True
        if result == r_no:
            return False
        assert result == r_cancel
        return None

    def ui_question_choice(self, text, options, multiple):
        '''Show an question with predefined choices.

        options is a list of strings to present. If multiple is True, they
        should be check boxes, if multiple is False they should be radio
        buttons.

        Return list of selected option indexes, or None if the user cancelled.
        If multiple == False, the list will always have one element.
        '''
        result = []
        dialog = CLIDialog(text, None)

        if multiple:
            while True:
                dialog = CLIDialog(text, None)
                index = 0
                choice_index_map = {}
                for option in options:
                    if index not in result:
                        choice_index_map[dialog.addbutton(option, str(index+1))] = index
                    index += 1
                done = dialog.addbutton(_('&Done'))
                cancel = dialog.addbutton(_('&Cancel'))

                if result:
                    cur = ', '.join([str(r+1) for r in result])
                else:
                    cur = _('none')
                response = dialog.run(_('Selected: %s. Multiple choices:') % cur)
                if response == cancel:
                    return None
                if response == done:
                    break
                result.append(choice_index_map[response])

        else:
            # single choice (radio button)
            dialog = CLIDialog(text, None)
            index = 1
            for option in options:
                dialog.addbutton(option, str(index))
                index += 1

            cancel = dialog.addbutton(_('&Cancel'))
            response = dialog.run(_('Choices:'))
            if response == cancel:
                return None
            result.append(response-1)

        return result

    def ui_question_file(self, text):
        '''Show a file selector dialog.

        Return path if the user selected a file, or None if cancelled.
        '''
        print('\n***  ' + text)
        while True:
            sys.stdout.write(_('Path to file (Enter to cancel):'))
            sys.stdout.write(' ')
            f = sys.stdin.readline().strip()
            if not f:
                return None
            if not os.path.exists(f):
                print(_('File does not exist.'))
            elif os.path.isdir(f):
                print(_('This is a directory.'))
            else:
                return f

    def open_url(self, url):
        text = '%s\n\n  %s\n\n%s' % (
            _('To continue, you must visit the following URL:'),
            url,
            _('You can launch a browser now, or copy this URL into a browser on another computer.'))

        answer = self.ui_question_choice(text, [_('Launch a browser now')], False)
        if answer == [0]:
            apport.ui.UserInterface.open_url(self, url)

    def ui_run_terminal(self, command):
        # we are already running in a terminal, so this works by definition
        if not command:
            return True

        subprocess.call(command, shell=True)

if __name__ == '__main__':
    app = CLIUserInterface()
    if not app.run_argv():
        print(_('No pending crash reports. Try --help for more information.'))