Revert "Fixed a use after free error when manipulating datasets."
[pspp] / src / ui / gui / psppire-text-file.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2017, 2020 Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include <gettext.h>
19 #define _(msgid) gettext (msgid)
20 #define P_(msgid) msgid
21
22 #include "psppire-text-file.h"
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26 #include <stdlib.h>
27 #include "libpspp/line-reader.h"
28 #include "libpspp/message.h"
29 #include "libpspp/str.h"
30 #include "libpspp/i18n.h"
31
32 #include <gtk/gtk.h>
33
34 /* Properties */
35 enum
36   {
37     PROP_0,
38     PROP_FILE_NAME,
39     PROP_ENCODING,
40     PROP_MAXIMUM_LINES,
41     PROP_LINE_COUNT
42   };
43
44 enum {MAX_LINE_LEN = 16384};  /* Max length of an acceptable line. */
45
46 static void
47 read_lines (PsppireTextFile *tf)
48 {
49   if (tf->file_name && 0 != g_strcmp0 ("unset", tf->encoding))
50     {
51       struct line_reader *reader = line_reader_for_file (tf->encoding, tf->file_name, O_RDONLY);
52
53       if (reader == NULL)
54         {
55           msg_error (errno, _("Could not open `%s'"),  tf->file_name);
56           return;
57         }
58
59       struct string input;
60       ds_init_empty (&input);
61       for (tf->line_cnt = 0; tf->line_cnt < MAX_PREVIEW_LINES; tf->line_cnt++)
62         {
63           ds_clear (&input);
64           if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
65               || ds_length (&input) > MAX_LINE_LEN)
66             {
67               int i;
68               if (line_reader_eof (reader))
69                 break;
70               else if (line_reader_error (reader))
71                 msg (ME, _("Error reading `%s': %s"),
72                      tf->file_name, strerror (line_reader_error (reader)));
73               else
74                 msg (ME, _("Failed to read `%s', because it contains a line "
75                            "over %d bytes long and therefore appears not to be "
76                            "a text file."),
77                      tf->file_name, MAX_LINE_LEN);
78               line_reader_close (reader);
79               for (i = 0; i < tf->line_cnt; i++)
80                 g_free (tf->lines[i].string);
81               tf->line_cnt = 0;
82               ds_destroy (&input);
83               return;
84             }
85
86           tf->lines[tf->line_cnt]
87             = recode_substring_pool ("UTF-8",
88                                      line_reader_get_encoding (reader),
89                                      input.ss, NULL);
90         }
91       ds_destroy (&input);
92
93       if (tf->line_cnt == 0)
94         {
95           int i;
96           msg (ME, _("`%s' is empty."), tf->file_name);
97           line_reader_close (reader);
98           for (i = 0; i < tf->line_cnt; i++)
99             g_free (tf->lines[i].string);
100           tf->line_cnt = 0;
101           goto done;
102         }
103
104       if (tf->line_cnt < MAX_PREVIEW_LINES)
105         {
106           tf->total_lines = tf->line_cnt;
107           tf->total_is_exact = true;
108         }
109       else
110         {
111           /* Estimate the number of lines in the file. */
112           struct stat s;
113           off_t position = line_reader_tell (reader);
114           if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
115             {
116               tf->total_lines = (double) tf->line_cnt / position * s.st_size;
117               tf->total_is_exact = false;
118             }
119           else
120             {
121               tf->total_lines = 0;
122               tf->total_is_exact = true;
123             }
124         }
125     done:
126       line_reader_close (reader);
127     }
128 }
129
130 static void
131 psppire_text_file_set_property (GObject         *object,
132                                 guint            prop_id,
133                                 const GValue    *value,
134                                 GParamSpec      *pspec)
135 {
136   PsppireTextFile *tf = PSPPIRE_TEXT_FILE (object);
137
138   switch (prop_id)
139     {
140     case PROP_MAXIMUM_LINES:
141       tf->maximum_lines = g_value_get_int (value);
142       break;
143     case PROP_FILE_NAME:
144       tf->file_name = g_value_dup_string (value);
145       read_lines (tf);
146       break;
147     case PROP_ENCODING:
148       g_free (tf->encoding);
149       tf->encoding = g_value_dup_string (value);
150       read_lines (tf);
151       break;
152     default:
153       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
154       break;
155     };
156
157 }
158
159 static void
160 psppire_text_file_get_property (GObject         *object,
161                                 guint            prop_id,
162                                 GValue          *value,
163                                 GParamSpec      *pspec)
164 {
165   PsppireTextFile *text_file = PSPPIRE_TEXT_FILE (object);
166
167   switch (prop_id)
168     {
169     case PROP_MAXIMUM_LINES:
170       g_value_set_int (value, text_file->maximum_lines);
171       break;
172     case PROP_LINE_COUNT:
173       g_value_set_int (value, text_file->line_cnt);
174       break;
175     case PROP_FILE_NAME:
176       g_value_set_string (value, text_file->file_name);
177       break;
178     case PROP_ENCODING:
179       g_value_set_string (value, text_file->encoding);
180       break;
181     default:
182       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
183       break;
184     };
185 }
186
187
188 static void psppire_text_file_init            (PsppireTextFile      *text_file);
189 static void psppire_text_file_class_init      (PsppireTextFileClass *class);
190
191 static void psppire_text_file_finalize        (GObject           *object);
192 static void psppire_text_file_dispose        (GObject           *object);
193
194 static GObjectClass *parent_class = NULL;
195
196 static gboolean
197 __tree_get_iter (GtkTreeModel *tree_model,
198                  GtkTreeIter *iter,
199                  GtkTreePath *path)
200 {
201   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
202
203   if (path == NULL)
204     return FALSE;
205
206   gint *indices = gtk_tree_path_get_indices (path);
207
208   gint n = *indices;
209
210   if (n >= file->line_cnt)
211     return FALSE;
212
213   iter->user_data = GINT_TO_POINTER (n);
214   iter->stamp = file->stamp;
215
216   return TRUE;
217 }
218
219
220 static gboolean
221 __tree_iter_next (GtkTreeModel *tree_model,
222                   GtkTreeIter *iter)
223 {
224   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
225   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
226
227   gint n = GPOINTER_TO_INT (iter->user_data) + 1;
228
229   if (n >= file->line_cnt)
230     return FALSE;
231
232   iter->user_data = GINT_TO_POINTER (n);
233
234   return TRUE;
235 }
236
237
238 static GType
239 __tree_get_column_type (GtkTreeModel *tree_model,
240                         gint          index)
241 {
242   if (index == 0)
243     return G_TYPE_INT;
244
245   return G_TYPE_STRING;
246 }
247
248 static gboolean
249 __iter_has_child (GtkTreeModel *tree_model,
250                   GtkTreeIter  *iter)
251 {
252   return 0;
253 }
254
255
256 static gboolean
257 __iter_parent     (GtkTreeModel *tree_model,
258                    GtkTreeIter  *iter,
259                    GtkTreeIter  *child)
260 {
261   return 0;
262 }
263
264 static GtkTreePath *
265 __tree_get_path (GtkTreeModel *tree_model,
266                  GtkTreeIter  *iter)
267 {
268   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
269   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
270
271   gint n = GPOINTER_TO_INT (iter->user_data);
272
273   return gtk_tree_path_new_from_indices (n, -1);
274 }
275
276
277 static gboolean
278 __iter_children (GtkTreeModel *tree_model,
279                               GtkTreeIter *iter,
280                               GtkTreeIter *parent)
281 {
282   return 0;
283 }
284
285
286 static gint
287 __tree_model_iter_n_children (GtkTreeModel *tree_model,
288                               GtkTreeIter *iter)
289 {
290   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
291   g_assert (iter == NULL);
292   return file->line_cnt;
293 }
294
295 static GtkTreeModelFlags
296 __tree_model_get_flags (GtkTreeModel *model)
297 {
298   g_return_val_if_fail (PSPPIRE_IS_TEXT_FILE (model), (GtkTreeModelFlags) 0);
299
300   return GTK_TREE_MODEL_LIST_ONLY;
301 }
302
303 static gint
304 __tree_model_get_n_columns (GtkTreeModel *tree_model)
305 {
306   return 2;
307 }
308
309
310 static gboolean
311 __iter_nth_child (GtkTreeModel *tree_model,
312                   GtkTreeIter *iter,
313                   GtkTreeIter *parent,
314                   gint n)
315 {
316   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
317
318   g_assert (parent == NULL);
319
320   g_return_val_if_fail (file, FALSE);
321
322   if (n >= file->line_cnt)
323     {
324       iter->stamp = -1;
325       iter->user_data = NULL;
326       return FALSE;
327     }
328
329   iter->user_data = GINT_TO_POINTER (n);
330   iter->stamp = file->stamp;
331
332   return TRUE;
333 }
334
335
336 static void
337 __get_value (GtkTreeModel *tree_model,
338              GtkTreeIter *iter,
339              gint column,
340              GValue *value)
341 {
342   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
343
344   g_return_if_fail (iter->stamp == file->stamp);
345
346   gint n = GPOINTER_TO_INT (iter->user_data);
347
348   g_return_if_fail (n < file->line_cnt);
349
350   if (column == 0)
351     {
352       g_value_init (value, G_TYPE_INT);
353       g_value_set_int (value, n + 1);
354       return;
355     }
356
357   g_value_init (value, G_TYPE_STRING);
358
359   if (column == 1)
360     {
361       char *s = ss_xstrdup (file->lines[n]);
362       g_value_set_string (value, s);
363       free (s);
364       return;
365     }
366
367   g_assert_not_reached ();
368 }
369
370
371 static void
372 __tree_model_init (GtkTreeModelIface *iface)
373 {
374   iface->get_flags       = __tree_model_get_flags;
375   iface->get_n_columns   = __tree_model_get_n_columns ;
376   iface->get_column_type = __tree_get_column_type;
377   iface->get_iter        = __tree_get_iter;
378   iface->iter_next       = __tree_iter_next;
379   iface->get_path        = __tree_get_path;
380   iface->get_value       = __get_value;
381
382   iface->iter_children   = __iter_children;
383   iface->iter_has_child  = __iter_has_child;
384   iface->iter_n_children = __tree_model_iter_n_children;
385   iface->iter_nth_child  = __iter_nth_child;
386   iface->iter_parent     = __iter_parent;
387 }
388
389
390 GType
391 psppire_text_file_get_type (void)
392 {
393   static GType text_file_type = 0;
394
395   if (!text_file_type)
396     {
397       static const GTypeInfo text_file_info =
398         {
399           sizeof (PsppireTextFileClass),
400           NULL,         /* base_init */
401           NULL,         /* base_finalize */
402           (GClassInitFunc) psppire_text_file_class_init,
403           NULL,         /* class_finalize */
404           NULL,         /* class_data */
405           sizeof (PsppireTextFile),
406           0,
407           (GInstanceInitFunc) psppire_text_file_init,
408         };
409
410       static const GInterfaceInfo tree_model_info = {
411         (GInterfaceInitFunc) __tree_model_init,
412         NULL,
413         NULL
414       };
415
416       text_file_type = g_type_register_static (G_TYPE_OBJECT,
417                                                "PsppireTextFile",
418                                                &text_file_info, 0);
419
420       g_type_add_interface_static (text_file_type, GTK_TYPE_TREE_MODEL,
421                                    &tree_model_info);
422     }
423
424   return text_file_type;
425 }
426
427
428 static void
429 psppire_text_file_class_init (PsppireTextFileClass *class)
430 {
431   GObjectClass *object_class;
432
433   parent_class = g_type_class_peek_parent (class);
434   object_class = (GObjectClass*) class;
435
436   GParamSpec *maximum_lines_spec =
437     g_param_spec_int ("maximum-lines",
438                       "Maximum Lines",
439                       P_("An upper limit on the number of lines to consider"),
440                       0, G_MAXINT, G_MAXINT,
441                       G_PARAM_READWRITE);
442
443   GParamSpec *line_count_spec =
444     g_param_spec_int ("line-count",
445                       "Line Count",
446                       P_("The number of lines in the file"),
447                       0, G_MAXINT, G_MAXINT,
448                       G_PARAM_READABLE);
449
450   GParamSpec *file_name_spec =
451     g_param_spec_string ("file-name",
452                          "File Name",
453                          P_("The name of the file from which this object was constructed"),
454                          NULL,
455                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
456
457   GParamSpec *encoding_spec =
458     g_param_spec_string ("encoding",
459                          "Character Encoding",
460                          P_("The character encoding of the file from which this object was constructed"),
461                          "unset",
462                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
463
464   object_class->set_property = psppire_text_file_set_property;
465   object_class->get_property = psppire_text_file_get_property;
466
467   g_object_class_install_property (object_class,
468                                    PROP_MAXIMUM_LINES,
469                                    maximum_lines_spec);
470
471   g_object_class_install_property (object_class,
472                                    PROP_LINE_COUNT,
473                                    line_count_spec);
474
475   g_object_class_install_property (object_class,
476                                    PROP_FILE_NAME,
477                                    file_name_spec);
478
479   g_object_class_install_property (object_class,
480                                    PROP_ENCODING,
481                                    encoding_spec);
482
483   object_class->finalize = psppire_text_file_finalize;
484   object_class->dispose = psppire_text_file_dispose;
485 }
486
487 static void
488 psppire_text_file_init (PsppireTextFile *text_file)
489 {
490   text_file->encoding = g_strdup ("unset");
491   text_file->file_name = NULL;
492
493   text_file->dispose_has_run = FALSE;
494   text_file->stamp = g_random_int ();
495 }
496
497
498 PsppireTextFile *
499 psppire_text_file_new (const gchar *file_name, const gchar *encoding)
500 {
501   PsppireTextFile *retval =
502     g_object_new (PSPPIRE_TYPE_TEXT_FILE,
503                   "file-name", file_name,
504                   "encoding", encoding,
505                   NULL);
506
507   return retval;
508 }
509
510 static void
511 psppire_text_file_finalize (GObject *object)
512 {
513   PsppireTextFile *tf = PSPPIRE_TEXT_FILE (object);
514
515   for (int i = 0; i < tf->line_cnt; i++)
516     g_free (tf->lines[i].string);
517
518   g_free (tf->encoding);
519   g_free (tf->file_name);
520
521   /* must chain up */
522   (* parent_class->finalize) (object);
523 }
524
525
526 static void
527 psppire_text_file_dispose (GObject *object)
528 {
529   PsppireTextFile *ds = PSPPIRE_TEXT_FILE (object);
530
531   if (ds->dispose_has_run)
532     return;
533
534   /* must chain up */
535   (* parent_class->dispose) (object);
536
537   ds->dispose_has_run = TRUE;
538 }
539
540 gboolean
541 psppire_text_file_get_total_exact (PsppireTextFile *tf)
542 {
543   return tf->total_is_exact;
544 }
545
546 gulong
547 psppire_text_file_get_n_lines (PsppireTextFile *tf)
548 {
549   return tf->total_lines;
550 }