1 /**
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  * SPDX-License-Identifier: Apache-2.0.
4  */
5 
6 #include <aws/io/stream.h>
7 
8 #include <aws/common/file.h>
9 #include <aws/io/file_utils.h>
10 
11 #include <errno.h>
12 
aws_input_stream_seek(struct aws_input_stream * stream,int64_t offset,enum aws_stream_seek_basis basis)13 int aws_input_stream_seek(struct aws_input_stream *stream, int64_t offset, enum aws_stream_seek_basis basis) {
14     AWS_ASSERT(stream && stream->vtable && stream->vtable->seek);
15 
16     return stream->vtable->seek(stream, offset, basis);
17 }
18 
aws_input_stream_read(struct aws_input_stream * stream,struct aws_byte_buf * dest)19 int aws_input_stream_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
20     AWS_ASSERT(stream && stream->vtable && stream->vtable->read);
21     AWS_ASSERT(dest);
22     AWS_ASSERT(dest->len <= dest->capacity);
23 
24     /* Deal with this edge case here, instead of relying on every implementation to do it right. */
25     if (dest->capacity == dest->len) {
26         return AWS_OP_SUCCESS;
27     }
28 
29     /* Prevent implementations from accidentally overwriting existing data in the buffer.
30      * Hand them a "safe" buffer that starts where the existing data ends. */
31     const void *safe_buf_start = dest->buffer + dest->len;
32     const size_t safe_buf_capacity = dest->capacity - dest->len;
33     struct aws_byte_buf safe_buf = aws_byte_buf_from_empty_array(safe_buf_start, safe_buf_capacity);
34 
35     int read_result = stream->vtable->read(stream, &safe_buf);
36 
37     /* Ensure the implementation did not commit forbidden acts upon the buffer */
38     AWS_FATAL_ASSERT(
39         (safe_buf.buffer == safe_buf_start) && (safe_buf.capacity == safe_buf_capacity) &&
40         (safe_buf.len <= safe_buf_capacity));
41 
42     if (read_result == AWS_OP_SUCCESS) {
43         /* Update the actual buffer */
44         dest->len += safe_buf.len;
45     }
46 
47     return read_result;
48 }
49 
aws_input_stream_get_status(struct aws_input_stream * stream,struct aws_stream_status * status)50 int aws_input_stream_get_status(struct aws_input_stream *stream, struct aws_stream_status *status) {
51     AWS_ASSERT(stream && stream->vtable && stream->vtable->get_status);
52 
53     return stream->vtable->get_status(stream, status);
54 }
55 
aws_input_stream_get_length(struct aws_input_stream * stream,int64_t * out_length)56 int aws_input_stream_get_length(struct aws_input_stream *stream, int64_t *out_length) {
57     AWS_ASSERT(stream && stream->vtable && stream->vtable->get_length);
58 
59     return stream->vtable->get_length(stream, out_length);
60 }
61 
aws_input_stream_destroy(struct aws_input_stream * stream)62 void aws_input_stream_destroy(struct aws_input_stream *stream) {
63     if (stream != NULL) {
64         AWS_ASSERT(stream->vtable && stream->vtable->destroy);
65 
66         stream->vtable->destroy(stream);
67     }
68 }
69 
70 /*
71  * cursor stream implementation
72  */
73 
74 struct aws_input_stream_byte_cursor_impl {
75     struct aws_byte_cursor original_cursor;
76     struct aws_byte_cursor current_cursor;
77 };
78 
79 /*
80  * This is an ugly function that, in the absence of better guidance, is designed to handle all possible combinations of
81  * size_t (uint32_t, uint64_t).  If size_t ever exceeds 64 bits this function will fail badly.
82  *
83  *  Safety and invariant assumptions are sprinkled via comments.  The overall strategy is to cast up to 64 bits and
84  * perform all arithmetic there, being careful with signed vs. unsigned to prevent bad operations.
85  *
86  *  Assumption #1: size_t resolves to an unsigned integer 64 bits or smaller
87  */
88 
89 AWS_STATIC_ASSERT(sizeof(size_t) <= 8);
90 
s_aws_input_stream_byte_cursor_seek(struct aws_input_stream * stream,int64_t offset,enum aws_stream_seek_basis basis)91 static int s_aws_input_stream_byte_cursor_seek(
92     struct aws_input_stream *stream,
93     int64_t offset,
94     enum aws_stream_seek_basis basis) {
95     struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
96 
97     uint64_t final_offset = 0;
98 
99     switch (basis) {
100         case AWS_SSB_BEGIN:
101             /*
102              * (uint64_t)offset -- safe by virtue of the earlier is-negative check
103              * (uint64_t)impl->original_cursor.len -- safe via assumption 1
104              */
105             if (offset < 0 || (uint64_t)offset > (uint64_t)impl->original_cursor.len) {
106                 return aws_raise_error(AWS_IO_STREAM_INVALID_SEEK_POSITION);
107             }
108 
109             /* safe because negative offsets were turned into an error */
110             final_offset = (uint64_t)offset;
111             break;
112 
113         case AWS_SSB_END:
114             /*
115              * -offset -- safe as long offset is not INT64_MIN which was previously checked
116              * (uint64_t)(-offset) -- safe because (-offset) is positive (and < INT64_MAX < UINT64_MAX)
117              */
118             if (offset > 0 || offset == INT64_MIN || (uint64_t)(-offset) > (uint64_t)impl->original_cursor.len) {
119                 return aws_raise_error(AWS_IO_STREAM_INVALID_SEEK_POSITION);
120             }
121 
122             /* cases that would make this unsafe became errors with previous conditional */
123             final_offset = (uint64_t)impl->original_cursor.len - (uint64_t)(-offset);
124             break;
125 
126         default:
127             return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
128     }
129 
130     /* true because we already validated against (impl->original_cursor.len) which is <= SIZE_MAX */
131     AWS_ASSERT(final_offset <= SIZE_MAX);
132 
133     /* safe via previous assert */
134     size_t final_offset_sz = (size_t)final_offset;
135 
136     /* sanity */
137     AWS_ASSERT(final_offset_sz <= impl->original_cursor.len);
138 
139     /* reset current_cursor to new position */
140     impl->current_cursor = impl->original_cursor;
141     impl->current_cursor.ptr += final_offset_sz;
142     impl->current_cursor.len -= final_offset_sz;
143 
144     return AWS_OP_SUCCESS;
145 }
146 
s_aws_input_stream_byte_cursor_read(struct aws_input_stream * stream,struct aws_byte_buf * dest)147 static int s_aws_input_stream_byte_cursor_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
148     struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
149 
150     size_t actually_read = dest->capacity - dest->len;
151     if (actually_read > impl->current_cursor.len) {
152         actually_read = impl->current_cursor.len;
153     }
154 
155     if (!aws_byte_buf_write(dest, impl->current_cursor.ptr, actually_read)) {
156         return aws_raise_error(AWS_IO_STREAM_READ_FAILED);
157     }
158 
159     aws_byte_cursor_advance(&impl->current_cursor, actually_read);
160 
161     return AWS_OP_SUCCESS;
162 }
163 
s_aws_input_stream_byte_cursor_get_status(struct aws_input_stream * stream,struct aws_stream_status * status)164 static int s_aws_input_stream_byte_cursor_get_status(
165     struct aws_input_stream *stream,
166     struct aws_stream_status *status) {
167     struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
168 
169     status->is_end_of_stream = impl->current_cursor.len == 0;
170     status->is_valid = true;
171 
172     return AWS_OP_SUCCESS;
173 }
174 
s_aws_input_stream_byte_cursor_get_length(struct aws_input_stream * stream,int64_t * out_length)175 static int s_aws_input_stream_byte_cursor_get_length(struct aws_input_stream *stream, int64_t *out_length) {
176     struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
177 
178 #if SIZE_MAX > INT64_MAX
179     size_t length = impl->original_cursor.len;
180     if (length > INT64_MAX) {
181         return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED);
182     }
183 #endif
184 
185     *out_length = (int64_t)impl->original_cursor.len;
186 
187     return AWS_OP_SUCCESS;
188 }
189 
s_aws_input_stream_byte_cursor_destroy(struct aws_input_stream * stream)190 static void s_aws_input_stream_byte_cursor_destroy(struct aws_input_stream *stream) {
191     aws_mem_release(stream->allocator, stream);
192 }
193 
194 static struct aws_input_stream_vtable s_aws_input_stream_byte_cursor_vtable = {
195     .seek = s_aws_input_stream_byte_cursor_seek,
196     .read = s_aws_input_stream_byte_cursor_read,
197     .get_status = s_aws_input_stream_byte_cursor_get_status,
198     .get_length = s_aws_input_stream_byte_cursor_get_length,
199     .destroy = s_aws_input_stream_byte_cursor_destroy};
200 
aws_input_stream_new_from_cursor(struct aws_allocator * allocator,const struct aws_byte_cursor * cursor)201 struct aws_input_stream *aws_input_stream_new_from_cursor(
202     struct aws_allocator *allocator,
203     const struct aws_byte_cursor *cursor) {
204 
205     struct aws_input_stream *input_stream = NULL;
206     struct aws_input_stream_byte_cursor_impl *impl = NULL;
207 
208     aws_mem_acquire_many(
209         allocator,
210         2,
211         &input_stream,
212         sizeof(struct aws_input_stream),
213         &impl,
214         sizeof(struct aws_input_stream_byte_cursor_impl));
215 
216     if (!input_stream) {
217         return NULL;
218     }
219 
220     AWS_ZERO_STRUCT(*input_stream);
221     AWS_ZERO_STRUCT(*impl);
222 
223     input_stream->allocator = allocator;
224     input_stream->vtable = &s_aws_input_stream_byte_cursor_vtable;
225     input_stream->impl = impl;
226 
227     impl->original_cursor = *cursor;
228     impl->current_cursor = *cursor;
229 
230     return input_stream;
231 }
232 
233 /*
234  * file-based input stream
235  */
236 struct aws_input_stream_file_impl {
237     FILE *file;
238     bool close_on_clean_up;
239 };
240 
s_aws_input_stream_file_seek(struct aws_input_stream * stream,int64_t offset,enum aws_stream_seek_basis basis)241 static int s_aws_input_stream_file_seek(
242     struct aws_input_stream *stream,
243     int64_t offset,
244     enum aws_stream_seek_basis basis) {
245     struct aws_input_stream_file_impl *impl = stream->impl;
246 
247     int whence = (basis == AWS_SSB_BEGIN) ? SEEK_SET : SEEK_END;
248     if (aws_fseek(impl->file, offset, whence)) {
249         return AWS_OP_ERR;
250     }
251 
252     return AWS_OP_SUCCESS;
253 }
254 
s_aws_input_stream_file_read(struct aws_input_stream * stream,struct aws_byte_buf * dest)255 static int s_aws_input_stream_file_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
256     struct aws_input_stream_file_impl *impl = stream->impl;
257 
258     size_t max_read = dest->capacity - dest->len;
259     size_t actually_read = fread(dest->buffer + dest->len, 1, max_read, impl->file);
260     if (actually_read == 0) {
261         if (ferror(impl->file)) {
262             return aws_raise_error(AWS_IO_STREAM_READ_FAILED);
263         }
264     }
265 
266     dest->len += actually_read;
267 
268     return AWS_OP_SUCCESS;
269 }
270 
s_aws_input_stream_file_get_status(struct aws_input_stream * stream,struct aws_stream_status * status)271 static int s_aws_input_stream_file_get_status(struct aws_input_stream *stream, struct aws_stream_status *status) {
272     struct aws_input_stream_file_impl *impl = stream->impl;
273 
274     status->is_end_of_stream = feof(impl->file) != 0;
275     status->is_valid = ferror(impl->file) == 0;
276 
277     return AWS_OP_SUCCESS;
278 }
279 
s_aws_input_stream_file_get_length(struct aws_input_stream * stream,int64_t * length)280 static int s_aws_input_stream_file_get_length(struct aws_input_stream *stream, int64_t *length) {
281     struct aws_input_stream_file_impl *impl = stream->impl;
282 
283     return aws_file_get_length(impl->file, length);
284 }
285 
s_aws_input_stream_file_destroy(struct aws_input_stream * stream)286 static void s_aws_input_stream_file_destroy(struct aws_input_stream *stream) {
287     struct aws_input_stream_file_impl *impl = stream->impl;
288 
289     if (impl->close_on_clean_up && impl->file) {
290         fclose(impl->file);
291     }
292 
293     aws_mem_release(stream->allocator, stream);
294 }
295 
296 static struct aws_input_stream_vtable s_aws_input_stream_file_vtable = {
297     .seek = s_aws_input_stream_file_seek,
298     .read = s_aws_input_stream_file_read,
299     .get_status = s_aws_input_stream_file_get_status,
300     .get_length = s_aws_input_stream_file_get_length,
301     .destroy = s_aws_input_stream_file_destroy};
302 
aws_input_stream_new_from_file(struct aws_allocator * allocator,const char * file_name)303 struct aws_input_stream *aws_input_stream_new_from_file(struct aws_allocator *allocator, const char *file_name) {
304 
305     struct aws_input_stream *input_stream = NULL;
306     struct aws_input_stream_file_impl *impl = NULL;
307 
308     aws_mem_acquire_many(
309         allocator, 2, &input_stream, sizeof(struct aws_input_stream), &impl, sizeof(struct aws_input_stream_file_impl));
310 
311     if (!input_stream) {
312         return NULL;
313     }
314 
315     AWS_ZERO_STRUCT(*input_stream);
316     AWS_ZERO_STRUCT(*impl);
317 
318     input_stream->allocator = allocator;
319     input_stream->vtable = &s_aws_input_stream_file_vtable;
320     input_stream->impl = impl;
321 
322     impl->file = aws_fopen(file_name, "r+b");
323     if (impl->file == NULL) {
324         aws_translate_and_raise_io_error(errno);
325         goto on_error;
326     }
327 
328     impl->close_on_clean_up = true;
329 
330     return input_stream;
331 
332 on_error:
333 
334     aws_input_stream_destroy(input_stream);
335 
336     return NULL;
337 }
338 
aws_input_stream_new_from_open_file(struct aws_allocator * allocator,FILE * file)339 struct aws_input_stream *aws_input_stream_new_from_open_file(struct aws_allocator *allocator, FILE *file) {
340     struct aws_input_stream *input_stream = NULL;
341     struct aws_input_stream_file_impl *impl = NULL;
342 
343     aws_mem_acquire_many(
344         allocator, 2, &input_stream, sizeof(struct aws_input_stream), &impl, sizeof(struct aws_input_stream_file_impl));
345 
346     if (!input_stream) {
347         return NULL;
348     }
349 
350     AWS_ZERO_STRUCT(*input_stream);
351     AWS_ZERO_STRUCT(*impl);
352 
353     input_stream->allocator = allocator;
354     input_stream->vtable = &s_aws_input_stream_file_vtable;
355     input_stream->impl = impl;
356 
357     impl->file = file;
358     impl->close_on_clean_up = false;
359 
360     return input_stream;
361 }
362