1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * GimpConfigWriter
5  * Copyright (C) 2003  Sven Neumann <sven@gimp.org>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <string.h>
25 
26 #include <gio/gio.h>
27 
28 #ifdef G_OS_WIN32
29 #include <gio/gwin32outputstream.h>
30 #else
31 #include <gio/gunixoutputstream.h>
32 #endif
33 
34 #include "libgimpbase/gimpbase.h"
35 
36 #include "gimpconfigtypes.h"
37 
38 #include "gimpconfigwriter.h"
39 #include "gimpconfig-iface.h"
40 #include "gimpconfig-error.h"
41 #include "gimpconfig-serialize.h"
42 #include "gimpconfig-utils.h"
43 
44 #include "libgimp/libgimp-intl.h"
45 
46 
47 /**
48  * SECTION: gimpconfigwriter
49  * @title: GimpConfigWriter
50  * @short_description: Functions for writing config info to a file for
51  *                     libgimpconfig.
52  *
53  * Functions for writing config info to a file for libgimpconfig.
54  **/
55 
56 
57 struct _GimpConfigWriter
58 {
59   GOutputStream *output;
60   GFile         *file;
61   GError        *error;
62   GString       *buffer;
63   gboolean       comment;
64   gint           depth;
65   gint           marker;
66 };
67 
68 
69 static inline void  gimp_config_writer_flush        (GimpConfigWriter  *writer);
70 static inline void  gimp_config_writer_newline      (GimpConfigWriter  *writer);
71 static gboolean     gimp_config_writer_close_output (GimpConfigWriter  *writer,
72                                                      GError           **error);
73 
74 static inline void
gimp_config_writer_flush(GimpConfigWriter * writer)75 gimp_config_writer_flush (GimpConfigWriter *writer)
76 {
77   GError *error = NULL;
78 
79   if (! writer->output)
80     return;
81 
82   if (! g_output_stream_write_all (writer->output,
83                                    writer->buffer->str,
84                                    writer->buffer->len,
85                                    NULL, NULL, &error))
86     {
87       g_set_error (&writer->error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE,
88                    _("Error writing to '%s': %s"),
89                    writer->file ?
90                    gimp_file_get_utf8_name (writer->file) : "output stream",
91                    error->message);
92       g_clear_error (&error);
93     }
94 
95   g_string_truncate (writer->buffer, 0);
96 }
97 
98 static inline void
gimp_config_writer_newline(GimpConfigWriter * writer)99 gimp_config_writer_newline (GimpConfigWriter *writer)
100 {
101   gint i;
102 
103   g_string_append_c (writer->buffer, '\n');
104 
105   if (writer->comment)
106     g_string_append_len (writer->buffer, "# ", 2);
107 
108   for (i = 0; i < writer->depth; i++)
109     g_string_append_len (writer->buffer, "    ", 4);
110 }
111 
112 /**
113  * gimp_config_writer_new_file:
114  * @filename: a filename
115  * @atomic: if %TRUE the file is written atomically
116  * @header: text to include as comment at the top of the file
117  * @error: return location for errors
118  *
119  * Creates a new #GimpConfigWriter and sets it up to write to
120  * @filename. If @atomic is %TRUE, a temporary file is used to avoid
121  * possible race conditions. The temporary file is then moved to
122  * @filename when the writer is closed.
123  *
124  * Return value: a new #GimpConfigWriter or %NULL in case of an error
125  *
126  * Since: 2.4
127  **/
128 GimpConfigWriter *
gimp_config_writer_new_file(const gchar * filename,gboolean atomic,const gchar * header,GError ** error)129 gimp_config_writer_new_file (const gchar  *filename,
130                              gboolean      atomic,
131                              const gchar  *header,
132                              GError      **error)
133 {
134   GimpConfigWriter *writer;
135   GFile            *file;
136 
137   g_return_val_if_fail (filename != NULL, NULL);
138   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
139 
140   file = g_file_new_for_path (filename);
141 
142   writer = gimp_config_writer_new_gfile (file, atomic, header, error);
143 
144   g_object_unref (file);
145 
146   return writer;
147 }
148 
149 /**
150  * gimp_config_writer_new_gfile:
151  * @file: a #GFile
152  * @atomic: if %TRUE the file is written atomically
153  * @header: text to include as comment at the top of the file
154  * @error: return location for errors
155  *
156  * Creates a new #GimpConfigWriter and sets it up to write to
157  * @file. If @atomic is %TRUE, a temporary file is used to avoid
158  * possible race conditions. The temporary file is then moved to @file
159  * when the writer is closed.
160  *
161  * Return value: a new #GimpConfigWriter or %NULL in case of an error
162  *
163  * Since: 2.10
164  **/
165 GimpConfigWriter *
gimp_config_writer_new_gfile(GFile * file,gboolean atomic,const gchar * header,GError ** error)166 gimp_config_writer_new_gfile (GFile        *file,
167                               gboolean      atomic,
168                               const gchar  *header,
169                               GError      **error)
170 {
171   GimpConfigWriter *writer;
172   GOutputStream    *output;
173   GFile            *dir;
174 
175   g_return_val_if_fail (G_IS_FILE (file), NULL);
176   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
177 
178   dir = g_file_get_parent (file);
179   if (dir && ! g_file_query_exists (dir, NULL))
180     {
181       if (! g_file_make_directory_with_parents (dir, NULL, error))
182         g_prefix_error (error,
183                         _("Could not create directory '%s' for '%s': "),
184                         gimp_file_get_utf8_name (dir),
185                         gimp_file_get_utf8_name (file));
186     }
187   g_object_unref (dir);
188 
189   if (error && *error)
190     return NULL;
191 
192   if (atomic)
193     {
194       output = G_OUTPUT_STREAM (g_file_replace (file,
195                                                 NULL, FALSE, G_FILE_CREATE_NONE,
196                                                 NULL, error));
197       if (! output)
198         g_prefix_error (error,
199                         _("Could not create temporary file for '%s': "),
200                         gimp_file_get_utf8_name (file));
201     }
202   else
203     {
204       output = G_OUTPUT_STREAM (g_file_replace (file,
205                                                 NULL, FALSE,
206                                                 G_FILE_CREATE_REPLACE_DESTINATION,
207                                                 NULL, error));
208     }
209 
210   if (! output)
211     return NULL;
212 
213   writer = g_slice_new0 (GimpConfigWriter);
214 
215   writer->output = output;
216   writer->file   = g_object_ref (file);
217   writer->buffer = g_string_new (NULL);
218 
219   if (header)
220     {
221       gimp_config_writer_comment (writer, header);
222       gimp_config_writer_linefeed (writer);
223     }
224 
225   return writer;
226 }
227 
228 /**
229  * gimp_config_writer_new_stream:
230  * @output: a #GOutputStream
231  * @header: text to include as comment at the top of the file
232  * @error: return location for errors
233  *
234  * Creates a new #GimpConfigWriter and sets it up to write to
235  * @output.
236  *
237  * Return value: a new #GimpConfigWriter or %NULL in case of an error
238  *
239  * Since: 2.10
240  **/
241 GimpConfigWriter *
gimp_config_writer_new_stream(GOutputStream * output,const gchar * header,GError ** error)242 gimp_config_writer_new_stream (GOutputStream  *output,
243                                const gchar    *header,
244                                GError        **error)
245 {
246   GimpConfigWriter *writer;
247 
248   g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), NULL);
249   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
250 
251   writer = g_slice_new0 (GimpConfigWriter);
252 
253   writer->output = g_object_ref (output);
254   writer->buffer = g_string_new (NULL);
255 
256   if (header)
257     {
258       gimp_config_writer_comment (writer, header);
259       gimp_config_writer_linefeed (writer);
260     }
261 
262   return writer;
263 }
264 
265 /**
266  * gimp_config_writer_new_fd:
267  * @fd:
268  *
269  * Return value: a new #GimpConfigWriter or %NULL in case of an error
270  *
271  * Since: 2.4
272  **/
273 GimpConfigWriter *
gimp_config_writer_new_fd(gint fd)274 gimp_config_writer_new_fd (gint fd)
275 {
276   GimpConfigWriter *writer;
277 
278   g_return_val_if_fail (fd > 0, NULL);
279 
280   writer = g_slice_new0 (GimpConfigWriter);
281 
282 #ifdef G_OS_WIN32
283   writer->output = g_win32_output_stream_new ((gpointer) fd, FALSE);
284 #else
285   writer->output = g_unix_output_stream_new (fd, FALSE);
286 #endif
287 
288   writer->buffer = g_string_new (NULL);
289 
290   return writer;
291 }
292 
293 /**
294  * gimp_config_writer_new_string:
295  * @string:
296  *
297  * Return value: a new #GimpConfigWriter or %NULL in case of an error
298  *
299  * Since: 2.4
300  **/
301 GimpConfigWriter *
gimp_config_writer_new_string(GString * string)302 gimp_config_writer_new_string (GString *string)
303 {
304   GimpConfigWriter *writer;
305 
306   g_return_val_if_fail (string != NULL, NULL);
307 
308   writer = g_slice_new0 (GimpConfigWriter);
309 
310   writer->buffer = string;
311 
312   return writer;
313 }
314 
315 /**
316  * gimp_config_writer_comment_mode:
317  * @writer: a #GimpConfigWriter
318  * @enable: %TRUE to enable comment mode, %FALSE to disable it
319  *
320  * This function toggles whether the @writer should create commented
321  * or uncommented output. This feature is used to generate the
322  * system-wide installed gimprc that documents the default settings.
323  *
324  * Since comments have to start at the beginning of a line, this
325  * function will insert a newline if necessary.
326  *
327  * Since: 2.4
328  **/
329 void
gimp_config_writer_comment_mode(GimpConfigWriter * writer,gboolean enable)330 gimp_config_writer_comment_mode (GimpConfigWriter *writer,
331                                  gboolean          enable)
332 {
333   g_return_if_fail (writer != NULL);
334 
335   if (writer->error)
336     return;
337 
338   enable = (enable ? TRUE : FALSE);
339 
340   if (writer->comment == enable)
341     return;
342 
343   writer->comment = enable;
344 
345   if (enable)
346     {
347      if (writer->buffer->len == 0)
348        g_string_append_len (writer->buffer, "# ", 2);
349      else
350        gimp_config_writer_newline (writer);
351     }
352 }
353 
354 
355 /**
356  * gimp_config_writer_open:
357  * @writer: a #GimpConfigWriter
358  * @name: name of the element to open
359  *
360  * This function writes the opening parenthesis followed by @name.
361  * It also increases the indentation level and sets a mark that
362  * can be used by gimp_config_writer_revert().
363  *
364  * Since: 2.4
365  **/
366 void
gimp_config_writer_open(GimpConfigWriter * writer,const gchar * name)367 gimp_config_writer_open (GimpConfigWriter *writer,
368                          const gchar      *name)
369 {
370   g_return_if_fail (writer != NULL);
371   g_return_if_fail (name != NULL);
372 
373   if (writer->error)
374     return;
375 
376   /* store the current buffer length so we can revert to this state */
377   writer->marker = writer->buffer->len;
378 
379   if (writer->depth > 0)
380     gimp_config_writer_newline (writer);
381 
382   writer->depth++;
383 
384   g_string_append_printf (writer->buffer, "(%s", name);
385 }
386 
387 /**
388  * gimp_config_writer_print:
389  * @writer: a #GimpConfigWriter
390  * @string: a string to write
391  * @len: number of bytes from @string or -1 if @string is NUL-terminated.
392  *
393  * Appends a space followed by @string to the @writer. Note that string
394  * must not contain any special characters that might need to be escaped.
395  *
396  * Since: 2.4
397  **/
398 void
gimp_config_writer_print(GimpConfigWriter * writer,const gchar * string,gint len)399 gimp_config_writer_print (GimpConfigWriter  *writer,
400                           const gchar       *string,
401                           gint               len)
402 {
403   g_return_if_fail (writer != NULL);
404   g_return_if_fail (len == 0 || string != NULL);
405 
406   if (writer->error)
407     return;
408 
409   if (len < 0)
410     len = strlen (string);
411 
412   if (len)
413     {
414       g_string_append_c (writer->buffer, ' ');
415       g_string_append_len (writer->buffer, string, len);
416     }
417 }
418 
419 /**
420  * gimp_config_writer_printf:
421  * @writer: a #GimpConfigWriter
422  * @format: a format string as described for g_strdup_printf().
423  * @...: list of arguments according to @format
424  *
425  * A printf-like function for #GimpConfigWriter.
426  *
427  * Since: 2.4
428  **/
429 void
gimp_config_writer_printf(GimpConfigWriter * writer,const gchar * format,...)430 gimp_config_writer_printf (GimpConfigWriter *writer,
431                            const gchar      *format,
432                            ...)
433 {
434   gchar   *buffer;
435   va_list  args;
436 
437   g_return_if_fail (writer != NULL);
438   g_return_if_fail (format != NULL);
439 
440   if (writer->error)
441     return;
442 
443   va_start (args, format);
444   buffer = g_strdup_vprintf (format, args);
445   va_end (args);
446 
447   g_string_append_c (writer->buffer, ' ');
448   g_string_append (writer->buffer, buffer);
449 
450   g_free (buffer);
451 }
452 
453 /**
454  * gimp_config_writer_string:
455  * @writer: a #GimpConfigWriter
456  * @string: a NUL-terminated string
457  *
458  * Writes a string value to @writer. The @string is quoted and special
459  * characters are escaped.
460  *
461  * Since: 2.4
462  **/
463 void
gimp_config_writer_string(GimpConfigWriter * writer,const gchar * string)464 gimp_config_writer_string (GimpConfigWriter *writer,
465                            const gchar      *string)
466 {
467   g_return_if_fail (writer != NULL);
468 
469   if (writer->error)
470     return;
471 
472   g_string_append_c (writer->buffer, ' ');
473   gimp_config_string_append_escaped (writer->buffer, string);
474 }
475 
476 /**
477  * gimp_config_writer_identifier:
478  * @writer:     a #GimpConfigWriter
479  * @identifier: a NUL-terminated string
480  *
481  * Writes an identifier to @writer. The @string is *not* quoted and special
482  * characters are *not* escaped.
483  *
484  * Since: 2.4
485  **/
486 void
gimp_config_writer_identifier(GimpConfigWriter * writer,const gchar * identifier)487 gimp_config_writer_identifier (GimpConfigWriter *writer,
488                                const gchar      *identifier)
489 {
490   g_return_if_fail (writer != NULL);
491   g_return_if_fail (identifier != NULL);
492 
493   if (writer->error)
494     return;
495 
496   g_string_append_printf (writer->buffer, " %s", identifier);
497 }
498 
499 
500 /**
501  * gimp_config_writer_data:
502  * @writer: a #GimpConfigWriter
503  * @length:
504  * @data:
505  *
506  * Since: 2.4
507  **/
508 void
gimp_config_writer_data(GimpConfigWriter * writer,gint length,const guint8 * data)509 gimp_config_writer_data (GimpConfigWriter *writer,
510                          gint              length,
511                          const guint8     *data)
512 {
513   gint i;
514 
515   g_return_if_fail (writer != NULL);
516   g_return_if_fail (length >= 0);
517   g_return_if_fail (data != NULL || length == 0);
518 
519   if (writer->error)
520     return;
521 
522   g_string_append (writer->buffer, " \"");
523 
524   for (i = 0; i < length; i++)
525     {
526       if (g_ascii_isalpha (data[i]))
527         g_string_append_c (writer->buffer, data[i]);
528       else
529         g_string_append_printf (writer->buffer, "\\%o", data[i]);
530     }
531 
532   g_string_append (writer->buffer, "\"");
533 }
534 
535 /**
536  * gimp_config_writer_revert:
537  * @writer: a #GimpConfigWriter
538  *
539  * Reverts all changes to @writer that were done since the last call
540  * to gimp_config_writer_open(). This can only work if you didn't call
541  * gimp_config_writer_close() yet.
542  *
543  * Since: 2.4
544  **/
545 void
gimp_config_writer_revert(GimpConfigWriter * writer)546 gimp_config_writer_revert (GimpConfigWriter *writer)
547 {
548   g_return_if_fail (writer != NULL);
549 
550   if (writer->error)
551     return;
552 
553   g_return_if_fail (writer->depth > 0);
554   g_return_if_fail (writer->marker != -1);
555 
556   g_string_truncate (writer->buffer, writer->marker);
557 
558   writer->depth--;
559   writer->marker = -1;
560 }
561 
562 /**
563  * gimp_config_writer_close:
564  * @writer: a #GimpConfigWriter
565  *
566  * Closes an element opened with gimp_config_writer_open().
567  *
568  * Since: 2.4
569  **/
570 void
gimp_config_writer_close(GimpConfigWriter * writer)571 gimp_config_writer_close (GimpConfigWriter *writer)
572 {
573   g_return_if_fail (writer != NULL);
574 
575   if (writer->error)
576     return;
577 
578   g_return_if_fail (writer->depth > 0);
579 
580   g_string_append_c (writer->buffer, ')');
581 
582   if (--writer->depth == 0)
583     {
584       g_string_append_c (writer->buffer, '\n');
585 
586       gimp_config_writer_flush (writer);
587     }
588 }
589 
590 /**
591  * gimp_config_writer_finish:
592  * @writer: a #GimpConfigWriter
593  * @footer: text to include as comment at the bottom of the file
594  * @error: return location for possible errors
595  *
596  * This function finishes the work of @writer and frees it afterwards.
597  * It closes all open elements, appends an optional comment and
598  * releases all resources allocated by @writer. You must not access
599  * the @writer afterwards.
600  *
601  * Return value: %TRUE if everything could be successfully written,
602  *               %FALSE otherwise
603  *
604  * Since: 2.4
605  **/
606 gboolean
gimp_config_writer_finish(GimpConfigWriter * writer,const gchar * footer,GError ** error)607 gimp_config_writer_finish (GimpConfigWriter  *writer,
608                            const gchar       *footer,
609                            GError           **error)
610 {
611   gboolean success = TRUE;
612 
613   g_return_val_if_fail (writer != NULL, FALSE);
614   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
615 
616   if (writer->depth < 0)
617     {
618       g_warning ("gimp_config_writer_finish: depth < 0 !!");
619     }
620   else
621     {
622       while (writer->depth)
623         gimp_config_writer_close (writer);
624     }
625 
626   if (footer)
627     {
628       gimp_config_writer_linefeed (writer);
629       gimp_config_writer_comment (writer, footer);
630     }
631 
632   if (writer->output)
633     {
634       success = gimp_config_writer_close_output (writer, error);
635 
636       if (writer->file)
637         g_object_unref (writer->file);
638 
639       g_string_free (writer->buffer, TRUE);
640     }
641 
642   if (writer->error)
643     {
644       if (error && *error == NULL)
645         g_propagate_error (error, writer->error);
646       else
647         g_clear_error (&writer->error);
648 
649       success = FALSE;
650     }
651 
652   g_slice_free (GimpConfigWriter, writer);
653 
654   return success;
655 }
656 
657 void
gimp_config_writer_linefeed(GimpConfigWriter * writer)658 gimp_config_writer_linefeed (GimpConfigWriter *writer)
659 {
660   g_return_if_fail (writer != NULL);
661 
662   if (writer->error)
663     return;
664 
665   if (writer->output && writer->buffer->len == 0 && !writer->comment)
666     {
667       GError *error = NULL;
668 
669       if (! g_output_stream_write_all (writer->output, "\n", 1,
670                                        NULL, NULL, &error))
671         {
672           g_set_error (&writer->error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE,
673                        _("Error writing to '%s': %s"),
674                        writer->file ?
675                        gimp_file_get_utf8_name (writer->file) : "output stream",
676                        error->message);
677           g_clear_error (&error);
678         }
679     }
680   else
681     {
682       gimp_config_writer_newline (writer);
683     }
684 }
685 
686 /**
687  * gimp_config_writer_comment:
688  * @writer: a #GimpConfigWriter
689  * @comment: the comment to write (ASCII only)
690  *
691  * Appends the @comment to @str and inserts linebreaks and hash-marks to
692  * format it as a comment. Note that this function does not handle non-ASCII
693  * characters.
694  *
695  * Since: 2.4
696  **/
697 void
gimp_config_writer_comment(GimpConfigWriter * writer,const gchar * comment)698 gimp_config_writer_comment (GimpConfigWriter *writer,
699                             const gchar      *comment)
700 {
701   const gchar *s;
702   gboolean     comment_mode;
703   gint         i, len, space;
704 
705 #define LINE_LENGTH 75
706 
707   g_return_if_fail (writer != NULL);
708 
709   if (writer->error)
710     return;
711 
712   g_return_if_fail (writer->depth == 0);
713 
714   if (!comment)
715     return;
716 
717   comment_mode = writer->comment;
718   gimp_config_writer_comment_mode (writer, TRUE);
719 
720   len = strlen (comment);
721 
722   while (len > 0)
723     {
724       for (s = comment, i = 0, space = 0;
725            *s != '\n' && (i <= LINE_LENGTH || space == 0) && i < len;
726            s++, i++)
727         {
728           if (g_ascii_isspace (*s))
729             space = i;
730         }
731 
732       if (i > LINE_LENGTH && space && *s != '\n')
733         i = space;
734 
735       g_string_append_len (writer->buffer, comment, i);
736 
737       i++;
738 
739       comment += i;
740       len     -= i;
741 
742       if (len > 0)
743         gimp_config_writer_newline (writer);
744     }
745 
746   gimp_config_writer_comment_mode (writer, comment_mode);
747   gimp_config_writer_newline (writer);
748 
749   if (writer->depth == 0)
750     gimp_config_writer_flush (writer);
751 
752 #undef LINE_LENGTH
753 }
754 
755 static gboolean
gimp_config_writer_close_output(GimpConfigWriter * writer,GError ** error)756 gimp_config_writer_close_output (GimpConfigWriter  *writer,
757                                  GError           **error)
758 {
759   g_return_val_if_fail (writer->output != NULL, FALSE);
760 
761   if (writer->error)
762     {
763       GCancellable *cancellable = g_cancellable_new ();
764 
765       /* Cancel the overwrite initiated by g_file_replace(). */
766       g_cancellable_cancel (cancellable);
767       g_output_stream_close (writer->output, cancellable, NULL);
768       g_object_unref (cancellable);
769 
770       g_object_unref (writer->output);
771       writer->output = NULL;
772 
773       return FALSE;
774     }
775 
776   if (writer->file)
777     {
778       GError *my_error = NULL;
779 
780       if (! g_output_stream_close (writer->output, NULL, &my_error))
781         {
782           g_set_error (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE,
783                        _("Error writing '%s': %s"),
784                        gimp_file_get_utf8_name (writer->file),
785                        my_error->message);
786           g_clear_error (&my_error);
787 
788           g_object_unref (writer->output);
789           writer->output = NULL;
790 
791           return FALSE;
792         }
793     }
794 
795   g_object_unref (writer->output);
796   writer->output = NULL;
797 
798   return TRUE;
799 }
800