1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* GMime
3 * Copyright (C) 2000-2014 Jeffrey Stedfast
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1
8 * of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free
17 * Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA.
19 */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <glib.h>
27
28 #ifdef G_OS_WIN32
29 #include <fcntl.h>
30 #include <io.h>
31 #endif
32 #include <errno.h>
33
34 #include "gmime-stream-file.h"
35
36
37 /**
38 * SECTION: gmime-stream-file
39 * @title: GMimeStreamFile
40 * @short_description: A Standard-C FILE-based stream
41 * @see_also: #GMimeStream
42 *
43 * A simple #GMimeStream implementation that sits on top of the
44 * Standard C FILE pointer based I/O layer. Unlike #GMimeStreamFs, a
45 * #GMimeStreamFile will typically buffer read and write operations at
46 * the FILE level and so it may be wasteful to wrap one in a
47 * #GMimeStreamBuffer stream.
48 **/
49
50
51 static void g_mime_stream_file_class_init (GMimeStreamFileClass *klass);
52 static void g_mime_stream_file_init (GMimeStreamFile *stream, GMimeStreamFileClass *klass);
53 static void g_mime_stream_file_finalize (GObject *object);
54
55 static ssize_t stream_read (GMimeStream *stream, char *buf, size_t len);
56 static ssize_t stream_write (GMimeStream *stream, const char *buf, size_t len);
57 static int stream_flush (GMimeStream *stream);
58 static int stream_close (GMimeStream *stream);
59 static gboolean stream_eos (GMimeStream *stream);
60 static int stream_reset (GMimeStream *stream);
61 static gint64 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence);
62 static gint64 stream_tell (GMimeStream *stream);
63 static gint64 stream_length (GMimeStream *stream);
64 static GMimeStream *stream_substream (GMimeStream *stream, gint64 start, gint64 end);
65
66
67 static GMimeStreamClass *parent_class = NULL;
68
69
70 GType
g_mime_stream_file_get_type(void)71 g_mime_stream_file_get_type (void)
72 {
73 static GType type = 0;
74
75 if (!type) {
76 static const GTypeInfo info = {
77 sizeof (GMimeStreamFileClass),
78 NULL, /* base_class_init */
79 NULL, /* base_class_finalize */
80 (GClassInitFunc) g_mime_stream_file_class_init,
81 NULL, /* class_finalize */
82 NULL, /* class_data */
83 sizeof (GMimeStreamFile),
84 0, /* n_preallocs */
85 (GInstanceInitFunc) g_mime_stream_file_init,
86 };
87
88 type = g_type_register_static (GMIME_TYPE_STREAM, "GMimeStreamFile", &info, 0);
89 }
90
91 return type;
92 }
93
94
95 static void
g_mime_stream_file_class_init(GMimeStreamFileClass * klass)96 g_mime_stream_file_class_init (GMimeStreamFileClass *klass)
97 {
98 GMimeStreamClass *stream_class = GMIME_STREAM_CLASS (klass);
99 GObjectClass *object_class = G_OBJECT_CLASS (klass);
100
101 parent_class = g_type_class_ref (GMIME_TYPE_STREAM);
102
103 object_class->finalize = g_mime_stream_file_finalize;
104
105 stream_class->read = stream_read;
106 stream_class->write = stream_write;
107 stream_class->flush = stream_flush;
108 stream_class->close = stream_close;
109 stream_class->eos = stream_eos;
110 stream_class->reset = stream_reset;
111 stream_class->seek = stream_seek;
112 stream_class->tell = stream_tell;
113 stream_class->length = stream_length;
114 stream_class->substream = stream_substream;
115 }
116
117 static void
g_mime_stream_file_init(GMimeStreamFile * stream,GMimeStreamFileClass * klass)118 g_mime_stream_file_init (GMimeStreamFile *stream, GMimeStreamFileClass *klass)
119 {
120 stream->owner = TRUE;
121 stream->fp = NULL;
122 }
123
124 static void
g_mime_stream_file_finalize(GObject * object)125 g_mime_stream_file_finalize (GObject *object)
126 {
127 GMimeStreamFile *stream = (GMimeStreamFile *) object;
128
129 if (stream->owner && stream->fp)
130 fclose (stream->fp);
131
132 G_OBJECT_CLASS (parent_class)->finalize (object);
133 }
134
135
136 static ssize_t
stream_read(GMimeStream * stream,char * buf,size_t len)137 stream_read (GMimeStream *stream, char *buf, size_t len)
138 {
139 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
140 size_t nread;
141
142 if (fstream->fp == NULL) {
143 errno = EBADF;
144 return -1;
145 }
146
147 if (stream->bound_end != -1 && stream->position >= stream->bound_end) {
148 errno = EINVAL;
149 return -1;
150 }
151
152 if (stream->bound_end != -1)
153 len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
154
155 /* make sure we are at the right position */
156 fseek (fstream->fp, (long) stream->position, SEEK_SET);
157
158 if ((nread = fread (buf, 1, len, fstream->fp)) > 0)
159 stream->position += nread;
160
161 return (ssize_t) nread;
162 }
163
164 static ssize_t
stream_write(GMimeStream * stream,const char * buf,size_t len)165 stream_write (GMimeStream *stream, const char *buf, size_t len)
166 {
167 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
168 size_t nwritten;
169
170 if (fstream->fp == NULL) {
171 errno = EBADF;
172 return -1;
173 }
174
175 if (stream->bound_end != -1 && stream->position >= stream->bound_end) {
176 errno = EINVAL;
177 return -1;
178 }
179
180 if (stream->bound_end != -1)
181 len = (size_t) MIN (stream->bound_end - stream->position, (gint64) len);
182
183 /* make sure we are at the right position */
184 fseek (fstream->fp, (long) stream->position, SEEK_SET);
185
186 if ((nwritten = fwrite (buf, 1, len, fstream->fp)) > 0)
187 stream->position += nwritten;
188
189 return (ssize_t) nwritten;
190 }
191
192 static int
stream_flush(GMimeStream * stream)193 stream_flush (GMimeStream *stream)
194 {
195 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
196
197 if (fstream->fp == NULL) {
198 errno = EBADF;
199 return -1;
200 }
201
202 return fflush (fstream->fp);
203 }
204
205 static int
stream_close(GMimeStream * stream)206 stream_close (GMimeStream *stream)
207 {
208 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
209 int rv;
210
211 if (fstream->fp == NULL)
212 return 0;
213
214 if ((rv = fclose (fstream->fp)) != 0)
215 fstream->fp = NULL;
216
217 return rv;
218 }
219
220 static gboolean
stream_eos(GMimeStream * stream)221 stream_eos (GMimeStream *stream)
222 {
223 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
224
225 if (fstream->fp == NULL)
226 return TRUE;
227
228 if (stream->bound_end == -1)
229 return feof (fstream->fp) ? TRUE : FALSE;
230 else
231 return stream->position >= stream->bound_end;
232 }
233
234 static int
stream_reset(GMimeStream * stream)235 stream_reset (GMimeStream *stream)
236 {
237 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
238
239 if (fstream->fp == NULL) {
240 errno = EBADF;
241 return -1;
242 }
243
244 if (stream->position == stream->bound_start)
245 return 0;
246
247 if (fseek (fstream->fp, (long) stream->bound_start, SEEK_SET) == -1)
248 return -1;
249
250 return 0;
251 }
252
253 static gint64
stream_seek(GMimeStream * stream,gint64 offset,GMimeSeekWhence whence)254 stream_seek (GMimeStream *stream, gint64 offset, GMimeSeekWhence whence)
255 {
256 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
257 gint64 real = stream->position;
258 FILE *fp = fstream->fp;
259
260 if (fstream->fp == NULL) {
261 errno = EBADF;
262 return -1;
263 }
264
265 switch (whence) {
266 case GMIME_STREAM_SEEK_SET:
267 real = offset;
268 break;
269 case GMIME_STREAM_SEEK_CUR:
270 real = stream->position + offset;
271 break;
272 case GMIME_STREAM_SEEK_END:
273 if (offset > 0 || (stream->bound_end == -1 && !feof (fp))) {
274 /* need to do an actual lseek() here because
275 * we either don't know the offset of the end
276 * of the stream and/or don't know if we can
277 * seek past the end */
278 if (fseek (fp, (long) offset, SEEK_END) == -1 || (real = ftell (fp)) == -1)
279 return -1;
280 } else if (feof (fp) && stream->bound_end == -1) {
281 /* seeking backwards from eos (which happens
282 * to be our current position) */
283 real = stream->position + offset;
284 } else {
285 /* seeking backwards from a known position */
286 real = stream->bound_end + offset;
287 }
288 break;
289 }
290
291 /* sanity check the resultant offset */
292 if (real < stream->bound_start) {
293 errno = EINVAL;
294 return -1;
295 }
296
297 if (stream->bound_end != -1 && real > stream->bound_end) {
298 errno = EINVAL;
299 return -1;
300 }
301
302 if (fseek (fp, (long) real, SEEK_SET) == -1 || (real = ftell (fp)) == -1)
303 return -1;
304
305 stream->position = real;
306
307 return real;
308 }
309
310 static gint64
stream_tell(GMimeStream * stream)311 stream_tell (GMimeStream *stream)
312 {
313 return stream->position;
314 }
315
316 static gint64
stream_length(GMimeStream * stream)317 stream_length (GMimeStream *stream)
318 {
319 GMimeStreamFile *fstream = (GMimeStreamFile *) stream;
320 gint64 bound_end;
321
322 if (fstream->fp == NULL) {
323 errno = EBADF;
324 return -1;
325 }
326
327 if (stream->bound_start != -1 && stream->bound_end != -1)
328 return stream->bound_end - stream->bound_start;
329
330 fseek (fstream->fp, (long) 0, SEEK_END);
331 bound_end = ftell (fstream->fp);
332 fseek (fstream->fp, (long) stream->position, SEEK_SET);
333
334 if (bound_end < stream->bound_start) {
335 errno = EINVAL;
336 return -1;
337 }
338
339 return bound_end - stream->bound_start;
340 }
341
342 static GMimeStream *
stream_substream(GMimeStream * stream,gint64 start,gint64 end)343 stream_substream (GMimeStream *stream, gint64 start, gint64 end)
344 {
345 GMimeStreamFile *fstream;
346
347 fstream = g_object_newv (GMIME_TYPE_STREAM_FILE, 0, NULL);
348 g_mime_stream_construct (GMIME_STREAM (fstream), start, end);
349 fstream->owner = FALSE;
350 fstream->fp = GMIME_STREAM_FILE (stream)->fp;
351
352 return GMIME_STREAM (fstream);
353 }
354
355
356 /**
357 * g_mime_stream_file_new:
358 * @fp: a FILE pointer
359 *
360 * Creates a new #GMimeStreamFile object around @fp.
361 *
362 * Note: The created #GMimeStreamFile object will own the FILE pointer
363 * passed in.
364 *
365 * Returns: a stream using @fp.
366 **/
367 GMimeStream *
g_mime_stream_file_new(FILE * fp)368 g_mime_stream_file_new (FILE *fp)
369 {
370 GMimeStreamFile *fstream;
371 gint64 start;
372
373 g_return_val_if_fail (fp != NULL, NULL);
374
375 #ifdef G_OS_WIN32
376 _setmode (_fileno (fp), O_BINARY);
377 #endif
378
379 if ((start = ftell (fp)) == -1)
380 start = 0;
381
382 fstream = g_object_newv (GMIME_TYPE_STREAM_FILE, 0, NULL);
383 g_mime_stream_construct (GMIME_STREAM (fstream), start, -1);
384 fstream->owner = TRUE;
385 fstream->fp = fp;
386
387 return GMIME_STREAM (fstream);
388 }
389
390
391 /**
392 * g_mime_stream_file_new_with_bounds:
393 * @fp: a FILE pointer
394 * @start: start boundary
395 * @end: end boundary
396 *
397 * Creates a new #GMimeStreamFile object around @fp with bounds @start
398 * and @end.
399 *
400 * Note: The created #GMimeStreamFile object will own the FILE pointer
401 * passed in.
402 *
403 * Returns: a stream using @fp with bounds @start and @end.
404 **/
405 GMimeStream *
g_mime_stream_file_new_with_bounds(FILE * fp,gint64 start,gint64 end)406 g_mime_stream_file_new_with_bounds (FILE *fp, gint64 start, gint64 end)
407 {
408 GMimeStreamFile *fstream;
409
410 g_return_val_if_fail (fp != NULL, NULL);
411
412 #ifdef G_OS_WIN32
413 _setmode (_fileno (fp), O_BINARY);
414 #endif
415
416 fstream = g_object_newv (GMIME_TYPE_STREAM_FILE, 0, NULL);
417 g_mime_stream_construct (GMIME_STREAM (fstream), start, end);
418 fstream->owner = TRUE;
419 fstream->fp = fp;
420
421 return GMIME_STREAM (fstream);
422 }
423
424
425 /**
426 * g_mime_stream_file_new_for_path:
427 * @path: the path to a file
428 * @mode: as in fopen(3)
429 *
430 * Creates a new #GMimeStreamFile object for the specified @path.
431 *
432 * Returns: a stream using for reading and/or writing to the specified
433 * file path or %NULL on error.
434 *
435 * Since: 2.6.18
436 **/
437 GMimeStream *
g_mime_stream_file_new_for_path(const char * path,const char * mode)438 g_mime_stream_file_new_for_path (const char *path, const char *mode)
439 {
440 FILE *fp;
441
442 g_return_val_if_fail (path != NULL, NULL);
443 g_return_val_if_fail (mode != NULL, NULL);
444
445 if (!(fp = fopen (path, mode)))
446 return NULL;
447
448 return g_mime_stream_file_new (fp);
449 }
450
451
452 /**
453 * g_mime_stream_file_get_owner:
454 * @stream: a #GMimeStreamFile
455 *
456 * Gets whether or not @stream owns the backend FILE pointer.
457 *
458 * Returns: %TRUE if @stream owns the backend FILE pointer or %FALSE
459 * otherwise.
460 **/
461 gboolean
g_mime_stream_file_get_owner(GMimeStreamFile * stream)462 g_mime_stream_file_get_owner (GMimeStreamFile *stream)
463 {
464 g_return_val_if_fail (GMIME_IS_STREAM_FILE (stream), FALSE);
465
466 return stream->owner;
467 }
468
469
470 /**
471 * g_mime_stream_file_set_owner:
472 * @stream: a #GMimeStreamFile
473 * @owner: %TRUE if this stream should own the FILE pointer or %FALSE otherwise
474 *
475 * Sets whether or not @stream owns the backend FILE pointer.
476 *
477 * Note: @owner should be %TRUE if the stream should fclose() the
478 * backend FILE pointer when destroyed or %FALSE otherwise.
479 **/
480 void
g_mime_stream_file_set_owner(GMimeStreamFile * stream,gboolean owner)481 g_mime_stream_file_set_owner (GMimeStreamFile *stream, gboolean owner)
482 {
483 g_return_if_fail (GMIME_IS_STREAM_FILE (stream));
484
485 stream->owner = owner;
486 }
487