1 /*
2  * pluma-document-output-stream.c
3  * This file is part of pluma
4  *
5  * Copyright (C) 2010 - Ignacio Casal Quinteiro
6  * Copyright (C) 2012-2021 MATE Developers
7  *
8  * pluma is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * pluma is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with pluma; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA  02110-1301  USA
22  */
23 
24 #include "config.h"
25 
26 #include <string.h>
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gio/gio.h>
30 #include "pluma-document-output-stream.h"
31 
32 /* NOTE: never use async methods on this stream, the stream is just
33  * a wrapper around GtkTextBuffer api so that we can use GIO Stream
34  * methods, but the undelying code operates on a GtkTextBuffer, so
35  * there is no I/O involved and should be accessed only by the main
36  * thread */
37 
38 #define MAX_UNICHAR_LEN 6
39 
40 struct _PlumaDocumentOutputStreamPrivate
41 {
42 	PlumaDocument *doc;
43 	GtkTextIter    pos;
44 
45 	gchar *buffer;
46 	gsize buflen;
47 
48 	guint is_initialized : 1;
49 	guint is_closed : 1;
50 };
51 
52 enum
53 {
54 	PROP_0,
55 	PROP_DOCUMENT
56 };
57 
58 G_DEFINE_TYPE_WITH_PRIVATE (PlumaDocumentOutputStream, pluma_document_output_stream, G_TYPE_OUTPUT_STREAM)
59 
60 static gssize	pluma_document_output_stream_write (GOutputStream            *stream,
61 						    const void               *buffer,
62 						    gsize                     count,
63 						    GCancellable             *cancellable,
64 						    GError                  **error);
65 
66 static gboolean pluma_document_output_stream_flush (GOutputStream *stream,
67                                                     GCancellable  *cancellable,
68                                                     GError        **error);
69 
70 static gboolean	pluma_document_output_stream_close (GOutputStream     *stream,
71 						    GCancellable      *cancellable,
72 						    GError           **error);
73 
74 static void
pluma_document_output_stream_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)75 pluma_document_output_stream_set_property (GObject      *object,
76 					   guint         prop_id,
77 					   const GValue *value,
78 					   GParamSpec   *pspec)
79 {
80 	PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
81 
82 	switch (prop_id)
83 	{
84 		case PROP_DOCUMENT:
85 			stream->priv->doc = PLUMA_DOCUMENT (g_value_get_object (value));
86 			break;
87 
88 		default:
89 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
90 			break;
91 	}
92 }
93 
94 static void
pluma_document_output_stream_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)95 pluma_document_output_stream_get_property (GObject    *object,
96 					   guint       prop_id,
97 					   GValue     *value,
98 					   GParamSpec *pspec)
99 {
100 	PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
101 
102 	switch (prop_id)
103 	{
104 		case PROP_DOCUMENT:
105 			g_value_set_object (value, stream->priv->doc);
106 			break;
107 
108 		default:
109 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
110 			break;
111 	}
112 }
113 
114 static void
pluma_document_output_stream_finalize(GObject * object)115 pluma_document_output_stream_finalize (GObject *object)
116 {
117 	PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
118 
119 	g_free (stream->priv->buffer);
120 
121 	G_OBJECT_CLASS (pluma_document_output_stream_parent_class)->finalize (object);
122 }
123 
124 static void
pluma_document_output_stream_constructed(GObject * object)125 pluma_document_output_stream_constructed (GObject *object)
126 {
127 	PlumaDocumentOutputStream *stream = PLUMA_DOCUMENT_OUTPUT_STREAM (object);
128 
129 	if (!stream->priv->doc)
130 	{
131 		g_critical ("This should never happen, a problem happened constructing the Document Output Stream!");
132 		return;
133 	}
134 
135 	/* Init the undoable action */
136 	gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
137 	/* clear the buffer */
138 	gtk_text_buffer_set_text (GTK_TEXT_BUFFER (stream->priv->doc),
139 				  "", 0);
140 	gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc),
141 				      FALSE);
142 
143 	gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
144 }
145 
146 static void
pluma_document_output_stream_class_init(PlumaDocumentOutputStreamClass * klass)147 pluma_document_output_stream_class_init (PlumaDocumentOutputStreamClass *klass)
148 {
149 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
150 	GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
151 
152 	object_class->get_property = pluma_document_output_stream_get_property;
153 	object_class->set_property = pluma_document_output_stream_set_property;
154 	object_class->finalize = pluma_document_output_stream_finalize;
155 	object_class->constructed = pluma_document_output_stream_constructed;
156 
157 	stream_class->write_fn = pluma_document_output_stream_write;
158 	stream_class->flush = pluma_document_output_stream_flush;
159 	stream_class->close_fn = pluma_document_output_stream_close;
160 
161 	g_object_class_install_property (object_class,
162 					 PROP_DOCUMENT,
163 					 g_param_spec_object ("document",
164 							      "Document",
165 							      "The document which is written",
166 							      PLUMA_TYPE_DOCUMENT,
167 							      G_PARAM_READWRITE |
168 							      G_PARAM_CONSTRUCT_ONLY));
169 }
170 
171 static void
pluma_document_output_stream_init(PlumaDocumentOutputStream * stream)172 pluma_document_output_stream_init (PlumaDocumentOutputStream *stream)
173 {
174 	stream->priv = pluma_document_output_stream_get_instance_private (stream);
175 
176 	stream->priv->buffer = NULL;
177 	stream->priv->buflen = 0;
178 
179 	stream->priv->is_initialized = FALSE;
180 	stream->priv->is_closed = FALSE;
181 }
182 
183 static PlumaDocumentNewlineType
get_newline_type(GtkTextIter * end)184 get_newline_type (GtkTextIter *end)
185 {
186 	PlumaDocumentNewlineType res;
187 	GtkTextIter copy;
188 	gunichar c;
189 
190 	copy = *end;
191 	c = gtk_text_iter_get_char (&copy);
192 
193 	if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN)
194 	{
195 		if (gtk_text_iter_forward_char (&copy) &&
196 		    g_unichar_break_type (gtk_text_iter_get_char (&copy)) == G_UNICODE_BREAK_LINE_FEED)
197 		{
198 			res = PLUMA_DOCUMENT_NEWLINE_TYPE_CR_LF;
199 		}
200 		else
201 		{
202 			res = PLUMA_DOCUMENT_NEWLINE_TYPE_CR;
203 		}
204 	}
205 	else
206 	{
207 		res = PLUMA_DOCUMENT_NEWLINE_TYPE_LF;
208 	}
209 
210 	return res;
211 }
212 
213 GOutputStream *
pluma_document_output_stream_new(PlumaDocument * doc)214 pluma_document_output_stream_new (PlumaDocument *doc)
215 {
216 	return G_OUTPUT_STREAM (g_object_new (PLUMA_TYPE_DOCUMENT_OUTPUT_STREAM,
217 					      "document", doc, NULL));
218 }
219 
220 PlumaDocumentNewlineType
pluma_document_output_stream_detect_newline_type(PlumaDocumentOutputStream * stream)221 pluma_document_output_stream_detect_newline_type (PlumaDocumentOutputStream *stream)
222 {
223 	PlumaDocumentNewlineType type;
224 	GtkTextIter iter;
225 
226 	g_return_val_if_fail (PLUMA_IS_DOCUMENT_OUTPUT_STREAM (stream),
227 			      PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT);
228 
229 	type = PLUMA_DOCUMENT_NEWLINE_TYPE_DEFAULT;
230 
231 	gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (stream->priv->doc),
232 					&iter);
233 
234 	if (gtk_text_iter_ends_line (&iter) || gtk_text_iter_forward_to_line_end (&iter))
235 	{
236 		type = get_newline_type (&iter);
237 	}
238 
239 	return type;
240 }
241 
242 /* If the last char is a newline, remove it from the buffer (otherwise
243    GtkTextView shows it as an empty line). See bug #324942. */
244 static void
remove_ending_newline(PlumaDocumentOutputStream * stream)245 remove_ending_newline (PlumaDocumentOutputStream *stream)
246 {
247 	GtkTextIter end;
248 	GtkTextIter start;
249 
250 	gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (stream->priv->doc), &end);
251 	start = end;
252 
253 	gtk_text_iter_set_line_offset (&start, 0);
254 
255 	if (gtk_text_iter_ends_line (&start) &&
256 	    gtk_text_iter_backward_line (&start))
257 	{
258 		if (!gtk_text_iter_ends_line (&start))
259 		{
260 			gtk_text_iter_forward_to_line_end (&start);
261 		}
262 
263 		/* Delete the empty line which is from 'start' to 'end' */
264 		gtk_text_buffer_delete (GTK_TEXT_BUFFER (stream->priv->doc),
265 		                        &start,
266 		                        &end);
267 	}
268 }
269 
270 static void
end_append_text_to_document(PlumaDocumentOutputStream * stream)271 end_append_text_to_document (PlumaDocumentOutputStream *stream)
272 {
273 	remove_ending_newline (stream);
274 
275 	gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc),
276 				      FALSE);
277 
278 	gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
279 }
280 
281 static gssize
pluma_document_output_stream_write(GOutputStream * stream,const void * buffer,gsize count,GCancellable * cancellable,GError ** error)282 pluma_document_output_stream_write (GOutputStream            *stream,
283 				    const void               *buffer,
284 				    gsize                     count,
285 				    GCancellable             *cancellable,
286 				    GError                  **error)
287 {
288 	PlumaDocumentOutputStream *ostream;
289 	gchar *text;
290 	gsize len;
291 	gboolean freetext = FALSE;
292 	const gchar *end;
293 	gboolean valid;
294 
295 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
296 		return -1;
297 
298 	ostream = PLUMA_DOCUMENT_OUTPUT_STREAM (stream);
299 
300 	if (!ostream->priv->is_initialized)
301 	{
302 		/* Init the undoable action */
303 		gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (ostream->priv->doc));
304 
305 		gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (ostream->priv->doc),
306 						&ostream->priv->pos);
307 		ostream->priv->is_initialized = TRUE;
308 	}
309 
310 	if (ostream->priv->buflen > 0)
311 	{
312 		len = ostream->priv->buflen + count;
313 		text = g_new (gchar , len + 1);
314 		memcpy (text, ostream->priv->buffer, ostream->priv->buflen);
315 		memcpy (text + ostream->priv->buflen, buffer, count);
316 		text[len] = '\0';
317 		g_free (ostream->priv->buffer);
318 		ostream->priv->buffer = NULL;
319 		ostream->priv->buflen = 0;
320 		freetext = TRUE;
321 	}
322 	else
323 	{
324 		text = (gchar *) buffer;
325 		len = count;
326 	}
327 
328 	/* validate */
329 	valid = g_utf8_validate (text, len, &end);
330 
331 	/* Avoid keeping a CRLF across two buffers. */
332 	if (valid && len > 1 && end[-1] == '\r')
333 	{
334 		valid = FALSE;
335 		end--;
336 	}
337 
338 	if (!valid)
339 	{
340 		gsize nvalid = end - text;
341 		gsize remainder = len - nvalid;
342 		gunichar ch;
343 
344 		if ((remainder < MAX_UNICHAR_LEN) &&
345 		    ((ch = g_utf8_get_char_validated (text + nvalid, remainder)) == (gunichar)-2 ||
346 		     ch == (gunichar)'\r'))
347 		{
348 			ostream->priv->buffer = g_strndup (end, remainder);
349 			ostream->priv->buflen = remainder;
350 			len -= remainder;
351 		}
352 		else
353 		{
354 			/* TODO: we could escape invalid text and tag it in red
355 			 * and make the doc readonly.
356 			 */
357 			g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
358 				     _("Invalid UTF-8 sequence in input"));
359 
360 			if (freetext)
361 				g_free (text);
362 
363 			return -1;
364 		}
365 	}
366 
367 	gtk_text_buffer_insert (GTK_TEXT_BUFFER (ostream->priv->doc),
368 				&ostream->priv->pos, text, len);
369 
370 	if (freetext)
371 		g_free (text);
372 
373 	return count;
374 }
375 
376 static gboolean
pluma_document_output_stream_flush(GOutputStream * stream,GCancellable * cancellable,GError ** error)377 pluma_document_output_stream_flush (GOutputStream *stream,
378                                     GCancellable  *cancellable,
379                                     GError        **error)
380 {
381 	PlumaDocumentOutputStream *ostream = PLUMA_DOCUMENT_OUTPUT_STREAM (stream);
382 
383 	/* Flush deferred data if some. */
384 	if (!ostream->priv->is_closed && ostream->priv->is_initialized &&
385 	    ostream->priv->buflen > 0 &&
386 	    pluma_document_output_stream_write (stream, "", 0, cancellable,
387 						error) == -1)
388 		return FALSE;
389 
390 	return TRUE;
391 }
392 
393 static gboolean
pluma_document_output_stream_close(GOutputStream * stream,GCancellable * cancellable,GError ** error)394 pluma_document_output_stream_close (GOutputStream     *stream,
395 				    GCancellable      *cancellable,
396 				    GError           **error)
397 {
398 	PlumaDocumentOutputStream *ostream = PLUMA_DOCUMENT_OUTPUT_STREAM (stream);
399 
400 	if (!ostream->priv->is_closed && ostream->priv->is_initialized)
401 	{
402 		end_append_text_to_document (ostream);
403 		ostream->priv->is_closed = TRUE;
404 	}
405 
406 	if (ostream->priv->buflen > 0)
407 	{
408 		g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
409 			     _("Incomplete UTF-8 sequence in input"));
410 		return FALSE;
411 	}
412 
413 	return TRUE;
414 }
415