summaryrefslogtreecommitdiffstats
path: root/src/librepgp/stream-common.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/librepgp/stream-common.cpp')
-rw-r--r--src/librepgp/stream-common.cpp1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/librepgp/stream-common.cpp b/src/librepgp/stream-common.cpp
new file mode 100644
index 0000000..334f93b
--- /dev/null
+++ b/src/librepgp/stream-common.cpp
@@ -0,0 +1,1212 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#include <string.h>
+#else
+#include "uniwin.h"
+#endif
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#include <rnp/rnp_def.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "types.h"
+#include "file-utils.h"
+#include "crypto/mem.h"
+#include <algorithm>
+#include <memory>
+
+bool
+src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ size_t left = len;
+ size_t read;
+ pgp_source_cache_t *cache = src->cache;
+ bool readahead = cache ? cache->readahead : false;
+
+ if (src->error) {
+ return false;
+ }
+
+ if (src->eof || (len == 0)) {
+ *readres = 0;
+ return true;
+ }
+
+ // Do not read more then available if source size is known
+ if (src->knownsize && (src->readb + len > src->size)) {
+ len = src->size - src->readb;
+ left = len;
+ readahead = false;
+ }
+
+ // Check whether we have cache and there is data inside
+ if (cache && (cache->len > cache->pos)) {
+ read = cache->len - cache->pos;
+ if (read >= len) {
+ memcpy(buf, &cache->buf[cache->pos], len);
+ cache->pos += len;
+ goto finish;
+ } else {
+ memcpy(buf, &cache->buf[cache->pos], read);
+ cache->pos += read;
+ buf = (uint8_t *) buf + read;
+ left = len - read;
+ }
+ }
+
+ // If we got here then we have empty cache or no cache at all
+ while (left > 0) {
+ if (left > sizeof(cache->buf) || !readahead || !cache) {
+ // If there is no cache or chunk is larger then read directly
+ if (!src->read(src, buf, left, &read)) {
+ src->error = 1;
+ return false;
+ }
+ if (!read) {
+ src->eof = 1;
+ len = len - left;
+ goto finish;
+ }
+ left -= read;
+ buf = (uint8_t *) buf + read;
+ } else {
+ // Try to fill the cache to avoid small reads
+ if (!src->read(src, &cache->buf[0], sizeof(cache->buf), &read)) {
+ src->error = 1;
+ return false;
+ }
+ if (!read) {
+ src->eof = 1;
+ len = len - left;
+ goto finish;
+ } else if (read < left) {
+ memcpy(buf, &cache->buf[0], read);
+ left -= read;
+ buf = (uint8_t *) buf + read;
+ } else {
+ memcpy(buf, &cache->buf[0], left);
+ cache->pos = left;
+ cache->len = read;
+ goto finish;
+ }
+ }
+ }
+
+finish:
+ src->readb += len;
+ if (src->knownsize && (src->readb == src->size)) {
+ src->eof = 1;
+ }
+ *readres = len;
+ return true;
+}
+
+bool
+src_read_eq(pgp_source_t *src, void *buf, size_t len)
+{
+ size_t res = 0;
+ return src_read(src, buf, len, &res) && (res == len);
+}
+
+bool
+src_peek(pgp_source_t *src, void *buf, size_t len, size_t *peeked)
+{
+ pgp_source_cache_t *cache = src->cache;
+ if (src->error) {
+ return false;
+ }
+ if (!cache || (len > sizeof(cache->buf))) {
+ return false;
+ }
+ if (src->eof) {
+ *peeked = 0;
+ return true;
+ }
+
+ size_t read = 0;
+ bool readahead = cache->readahead;
+ // Do not read more then available if source size is known
+ if (src->knownsize && (src->readb + len > src->size)) {
+ len = src->size - src->readb;
+ readahead = false;
+ }
+
+ if (cache->len - cache->pos >= len) {
+ if (buf) {
+ memcpy(buf, &cache->buf[cache->pos], len);
+ }
+ *peeked = len;
+ return true;
+ }
+
+ if (cache->pos > 0) {
+ memmove(&cache->buf[0], &cache->buf[cache->pos], cache->len - cache->pos);
+ cache->len -= cache->pos;
+ cache->pos = 0;
+ }
+
+ while (cache->len < len) {
+ read = readahead ? sizeof(cache->buf) - cache->len : len - cache->len;
+ if (src->knownsize && (src->readb + read > src->size)) {
+ read = src->size - src->readb;
+ }
+ if (!src->read(src, &cache->buf[cache->len], read, &read)) {
+ src->error = 1;
+ return false;
+ }
+ if (!read) {
+ if (buf) {
+ memcpy(buf, &cache->buf[0], cache->len);
+ }
+ *peeked = cache->len;
+ return true;
+ }
+ cache->len += read;
+ if (cache->len >= len) {
+ if (buf) {
+ memcpy(buf, cache->buf, len);
+ }
+ *peeked = len;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+src_peek_eq(pgp_source_t *src, void *buf, size_t len)
+{
+ size_t res = 0;
+ return src_peek(src, buf, len, &res) && (res == len);
+}
+
+void
+src_skip(pgp_source_t *src, size_t len)
+{
+ if (src->cache && (src->cache->len - src->cache->pos >= len)) {
+ src->readb += len;
+ src->cache->pos += len;
+ return;
+ }
+
+ size_t res = 0;
+ uint8_t sbuf[16];
+ if (len < sizeof(sbuf)) {
+ (void) src_read(src, sbuf, len, &res);
+ return;
+ }
+ if (src_eof(src)) {
+ return;
+ }
+
+ void *buf = calloc(1, std::min((size_t) PGP_INPUT_CACHE_SIZE, len));
+ if (!buf) {
+ src->error = 1;
+ return;
+ }
+
+ while (len && !src_eof(src)) {
+ if (!src_read(src, buf, std::min((size_t) PGP_INPUT_CACHE_SIZE, len), &res)) {
+ break;
+ }
+ len -= res;
+ }
+ free(buf);
+}
+
+rnp_result_t
+src_finish(pgp_source_t *src)
+{
+ rnp_result_t res = RNP_SUCCESS;
+ if (src->finish) {
+ res = src->finish(src);
+ }
+
+ return res;
+}
+
+bool
+src_error(const pgp_source_t *src)
+{
+ return src->error;
+}
+
+bool
+src_eof(pgp_source_t *src)
+{
+ if (src->eof) {
+ return true;
+ }
+ /* Error on stream read is NOT considered as eof. See src_error(). */
+ uint8_t check;
+ size_t read = 0;
+ return src_peek(src, &check, 1, &read) && (read == 0);
+}
+
+void
+src_close(pgp_source_t *src)
+{
+ if (src->close) {
+ src->close(src);
+ }
+
+ if (src->cache) {
+ free(src->cache);
+ src->cache = NULL;
+ }
+}
+
+bool
+src_skip_eol(pgp_source_t *src)
+{
+ uint8_t eol[2];
+ size_t read;
+
+ if (!src_peek(src, eol, 2, &read) || !read) {
+ return false;
+ }
+ if (eol[0] == '\n') {
+ src_skip(src, 1);
+ return true;
+ }
+ if ((read == 2) && (eol[0] == '\r') && (eol[1] == '\n')) {
+ src_skip(src, 2);
+ return true;
+ }
+ return false;
+}
+
+bool
+src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *readres)
+{
+ size_t scan_pos = 0;
+ size_t inc = 64;
+ len = len - 1;
+
+ do {
+ size_t to_peek = scan_pos + inc;
+ to_peek = to_peek > len ? len : to_peek;
+ inc = inc * 2;
+
+ /* inefficient, each time we again read from the beginning */
+ if (!src_peek(src, buf, to_peek, readres)) {
+ return false;
+ }
+
+ /* we continue scanning where we stopped previously */
+ for (; scan_pos < *readres; scan_pos++) {
+ if (buf[scan_pos] == '\n') {
+ if ((scan_pos > 0) && (buf[scan_pos - 1] == '\r')) {
+ scan_pos--;
+ }
+ buf[scan_pos] = '\0';
+ *readres = scan_pos;
+ return true;
+ }
+ }
+ if (*readres < to_peek) {
+ return false;
+ }
+ } while (scan_pos < len);
+ return false;
+}
+
+bool
+init_src_common(pgp_source_t *src, size_t paramsize)
+{
+ memset(src, 0, sizeof(*src));
+ src->cache = (pgp_source_cache_t *) calloc(1, sizeof(*src->cache));
+ if (!src->cache) {
+ RNP_LOG("cache allocation failed");
+ return false;
+ }
+ src->cache->readahead = true;
+ if (!paramsize) {
+ return true;
+ }
+ src->param = calloc(1, paramsize);
+ if (!src->param) {
+ RNP_LOG("param allocation failed");
+ free(src->cache);
+ src->cache = NULL;
+ return false;
+ }
+ return true;
+}
+
+typedef struct pgp_source_file_param_t {
+ int fd;
+} pgp_source_file_param_t;
+
+static bool
+file_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ int64_t rres = read(param->fd, buf, len);
+ if (rres < 0) {
+ return false;
+ }
+ *readres = rres;
+ return true;
+}
+
+static void
+file_src_close(pgp_source_t *src)
+{
+ pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param;
+ if (param) {
+ if (src->type == PGP_STREAM_FILE) {
+ close(param->fd);
+ }
+ free(src->param);
+ src->param = NULL;
+ }
+}
+
+static rnp_result_t
+init_fd_src(pgp_source_t *src, int fd, uint64_t *size)
+{
+ if (!init_src_common(src, sizeof(pgp_source_file_param_t))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param;
+ param->fd = fd;
+ src->read = file_src_read;
+ src->close = file_src_close;
+ src->type = PGP_STREAM_FILE;
+ src->size = size ? *size : 0;
+ src->knownsize = !!size;
+
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_file_src(pgp_source_t *src, const char *path)
+{
+ int fd;
+ struct stat st;
+
+ if (rnp_stat(path, &st) != 0) {
+ RNP_LOG("can't stat '%s'", path);
+ return RNP_ERROR_READ;
+ }
+
+ /* read call may succeed on directory depending on OS type */
+ if (S_ISDIR(st.st_mode)) {
+ RNP_LOG("source is directory");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ int flags = O_RDONLY;
+#ifdef HAVE_O_BINARY
+ flags |= O_BINARY;
+#else
+#ifdef HAVE__O_BINARY
+ flags |= _O_BINARY;
+#endif
+#endif
+ fd = rnp_open(path, flags, 0);
+
+ if (fd < 0) {
+ RNP_LOG("can't open '%s'", path);
+ return RNP_ERROR_READ;
+ }
+ uint64_t size = st.st_size;
+ rnp_result_t ret = init_fd_src(src, fd, &size);
+ if (ret) {
+ close(fd);
+ }
+ return ret;
+}
+
+rnp_result_t
+init_stdin_src(pgp_source_t *src)
+{
+ pgp_source_file_param_t *param;
+
+ if (!init_src_common(src, sizeof(pgp_source_file_param_t))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_source_file_param_t *) src->param;
+ param->fd = 0;
+ src->read = file_src_read;
+ src->close = file_src_close;
+ src->type = PGP_STREAM_STDIN;
+
+ return RNP_SUCCESS;
+}
+
+typedef struct pgp_source_mem_param_t {
+ const void *memory;
+ bool free;
+ size_t len;
+ size_t pos;
+} pgp_source_mem_param_t;
+
+typedef struct pgp_dest_mem_param_t {
+ unsigned maxalloc;
+ unsigned allocated;
+ void * memory;
+ bool free;
+ bool discard_overflow;
+ bool secure;
+} pgp_dest_mem_param_t;
+
+static bool
+mem_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ if (len > param->len - param->pos) {
+ len = param->len - param->pos;
+ }
+ memcpy(buf, (uint8_t *) param->memory + param->pos, len);
+ param->pos += len;
+ *read = len;
+ return true;
+}
+
+static void
+mem_src_close(pgp_source_t *src)
+{
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ if (param) {
+ if (param->free) {
+ free((void *) param->memory);
+ }
+ free(src->param);
+ src->param = NULL;
+ }
+}
+
+rnp_result_t
+init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free)
+{
+ if (!mem && len) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* this is actually double buffering, but then src_peek will fail */
+ if (!init_src_common(src, sizeof(pgp_source_mem_param_t))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ param->memory = mem;
+ param->len = len;
+ param->pos = 0;
+ param->free = free;
+ src->read = mem_src_read;
+ src->close = mem_src_close;
+ src->finish = NULL;
+ src->size = len;
+ src->knownsize = 1;
+ src->type = PGP_STREAM_MEMORY;
+
+ return RNP_SUCCESS;
+}
+
+static bool
+null_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ return false;
+}
+
+rnp_result_t
+init_null_src(pgp_source_t *src)
+{
+ memset(src, 0, sizeof(*src));
+ src->read = null_src_read;
+ src->type = PGP_STREAM_NULL;
+ src->error = true;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+read_mem_src(pgp_source_t *src, pgp_source_t *readsrc)
+{
+ pgp_dest_t dst;
+ rnp_result_t ret;
+
+ if ((ret = init_mem_dest(&dst, NULL, 0))) {
+ return ret;
+ }
+
+ if ((ret = dst_write_src(readsrc, &dst))) {
+ goto done;
+ }
+
+ if ((ret = init_mem_src(src, mem_dest_own_memory(&dst), dst.writeb, true))) {
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ dst_close(&dst, true);
+ return ret;
+}
+
+rnp_result_t
+file_to_mem_src(pgp_source_t *src, const char *filename)
+{
+ pgp_source_t fsrc = {};
+ rnp_result_t res = RNP_ERROR_GENERIC;
+
+ if ((res = init_file_src(&fsrc, filename))) {
+ return res;
+ }
+
+ res = read_mem_src(src, &fsrc);
+ src_close(&fsrc);
+
+ return res;
+}
+
+const void *
+mem_src_get_memory(pgp_source_t *src, bool own)
+{
+ if (src->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return NULL;
+ }
+
+ if (!src->param) {
+ return NULL;
+ }
+
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ if (own) {
+ param->free = false;
+ }
+ return param->memory;
+}
+
+bool
+init_dst_common(pgp_dest_t *dst, size_t paramsize)
+{
+ memset(dst, 0, sizeof(*dst));
+ dst->werr = RNP_SUCCESS;
+ if (!paramsize) {
+ return true;
+ }
+ /* allocate param */
+ dst->param = calloc(1, paramsize);
+ if (!dst->param) {
+ RNP_LOG("allocation failed");
+ }
+ return dst->param;
+}
+
+void
+dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ /* we call write function only if all previous calls succeeded */
+ if ((len > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) {
+ /* if cache non-empty and len will overflow it then fill it and write out */
+ if ((dst->clen > 0) && (dst->clen + len > sizeof(dst->cache))) {
+ memcpy(dst->cache + dst->clen, buf, sizeof(dst->cache) - dst->clen);
+ buf = (uint8_t *) buf + sizeof(dst->cache) - dst->clen;
+ len -= sizeof(dst->cache) - dst->clen;
+ dst->werr = dst->write(dst, dst->cache, sizeof(dst->cache));
+ dst->writeb += sizeof(dst->cache);
+ dst->clen = 0;
+ if (dst->werr != RNP_SUCCESS) {
+ return;
+ }
+ }
+
+ /* here everything will fit into the cache or cache is empty */
+ if (dst->no_cache || (len > sizeof(dst->cache))) {
+ dst->werr = dst->write(dst, buf, len);
+ if (!dst->werr) {
+ dst->writeb += len;
+ }
+ } else {
+ memcpy(dst->cache + dst->clen, buf, len);
+ dst->clen += len;
+ }
+ }
+}
+
+void
+dst_printf(pgp_dest_t *dst, const char *format, ...)
+{
+ char buf[2048];
+ size_t len;
+ va_list ap;
+
+ va_start(ap, format);
+ len = vsnprintf(buf, sizeof(buf), format, ap);
+ va_end(ap);
+
+ if (len >= sizeof(buf)) {
+ RNP_LOG("too long dst_printf");
+ len = sizeof(buf) - 1;
+ }
+ dst_write(dst, buf, len);
+}
+
+void
+dst_flush(pgp_dest_t *dst)
+{
+ if ((dst->clen > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) {
+ dst->werr = dst->write(dst, dst->cache, dst->clen);
+ dst->writeb += dst->clen;
+ dst->clen = 0;
+ }
+}
+
+rnp_result_t
+dst_finish(pgp_dest_t *dst)
+{
+ rnp_result_t res = RNP_SUCCESS;
+
+ if (!dst->finished) {
+ /* flush write cache in the dst */
+ dst_flush(dst);
+ if (dst->finish) {
+ res = dst->finish(dst);
+ }
+ dst->finished = true;
+ }
+
+ return res;
+}
+
+void
+dst_close(pgp_dest_t *dst, bool discard)
+{
+ if (!discard && !dst->finished) {
+ dst_finish(dst);
+ }
+
+ if (dst->close) {
+ dst->close(dst, discard);
+ }
+}
+
+typedef struct pgp_dest_file_param_t {
+ int fd;
+ int errcode;
+ bool overwrite;
+ std::string path;
+} pgp_dest_file_param_t;
+
+static rnp_result_t
+file_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* we assyme that blocking I/O is used so everything is written or error received */
+ ssize_t ret = write(param->fd, buf, len);
+ if (ret < 0) {
+ param->errcode = errno;
+ RNP_LOG("write failed, error %d", param->errcode);
+ return RNP_ERROR_WRITE;
+ } else {
+ param->errcode = 0;
+ return RNP_SUCCESS;
+ }
+}
+
+static void
+file_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ if (dst->type == PGP_STREAM_FILE) {
+ close(param->fd);
+ if (discard) {
+ rnp_unlink(param->path.c_str());
+ }
+ }
+
+ delete param;
+ dst->param = NULL;
+}
+
+static rnp_result_t
+init_fd_dest(pgp_dest_t *dst, int fd, const char *path)
+{
+ if (!init_dst_common(dst, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ try {
+ std::unique_ptr<pgp_dest_file_param_t> param(new pgp_dest_file_param_t());
+ param->path = path;
+ param->fd = fd;
+ dst->param = param.release();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ dst->write = file_dst_write;
+ dst->close = file_dst_close;
+ dst->type = PGP_STREAM_FILE;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite)
+{
+ /* check whether file/dir already exists */
+ struct stat st;
+ if (!rnp_stat(path, &st)) {
+ if (!overwrite) {
+ RNP_LOG("file already exists: '%s'", path);
+ return RNP_ERROR_WRITE;
+ }
+
+ /* if we are overwriting empty directory then should first remove it */
+ if (S_ISDIR(st.st_mode)) {
+ if (rmdir(path) == -1) {
+ RNP_LOG("failed to remove directory: error %d", errno);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+ }
+
+ int flags = O_WRONLY | O_CREAT;
+ flags |= overwrite ? O_TRUNC : O_EXCL;
+#ifdef HAVE_O_BINARY
+ flags |= O_BINARY;
+#else
+#ifdef HAVE__O_BINARY
+ flags |= _O_BINARY;
+#endif
+#endif
+ int fd = rnp_open(path, flags, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ RNP_LOG("failed to create file '%s'. Error %d.", path, errno);
+ return RNP_ERROR_WRITE;
+ }
+
+ rnp_result_t res = init_fd_dest(dst, fd, path);
+ if (res) {
+ close(fd);
+ }
+ return res;
+}
+
+#define TMPDST_SUFFIX ".rnp-tmp.XXXXXX"
+
+static rnp_result_t
+file_tmpdst_finish(pgp_dest_t *dst)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ if (!param) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* close the file */
+ close(param->fd);
+ param->fd = -1;
+
+ /* rename the temporary file */
+ if (param->path.size() < strlen(TMPDST_SUFFIX)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ try {
+ /* remove suffix so we have required path */
+ std::string origpath(param->path.begin(), param->path.end() - strlen(TMPDST_SUFFIX));
+ /* check if file already exists */
+ struct stat st;
+ if (!rnp_stat(origpath.c_str(), &st)) {
+ if (!param->overwrite) {
+ RNP_LOG("target path already exists");
+ return RNP_ERROR_BAD_STATE;
+ }
+#ifdef _WIN32
+ /* rename() call on Windows fails if destination exists */
+ else {
+ rnp_unlink(origpath.c_str());
+ }
+#endif
+
+ /* we should remove dir if overwriting, file will be unlinked in rename call */
+ if (S_ISDIR(st.st_mode) && rmdir(origpath.c_str())) {
+ RNP_LOG("failed to remove directory");
+ return RNP_ERROR_BAD_STATE;
+ }
+ }
+
+ if (rnp_rename(param->path.c_str(), origpath.c_str())) {
+ RNP_LOG("failed to rename temporary path to target file: %s", strerror(errno));
+ return RNP_ERROR_BAD_STATE;
+ }
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+}
+
+static void
+file_tmpdst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ /* we close file in finish function, except the case when some error occurred */
+ if (!dst->finished && (dst->type == PGP_STREAM_FILE)) {
+ close(param->fd);
+ if (discard) {
+ rnp_unlink(param->path.c_str());
+ }
+ }
+
+ delete param;
+ dst->param = NULL;
+}
+
+rnp_result_t
+init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite)
+{
+ try {
+ std::string tmp = std::string(path) + std::string(TMPDST_SUFFIX);
+ /* make sure tmp.data() is zero-terminated */
+ tmp.push_back('\0');
+#if defined(HAVE_MKSTEMP) && !defined(_WIN32)
+ int fd = mkstemp(&tmp[0]);
+#else
+ int fd = rnp_mkstemp(&tmp[0]);
+#endif
+ if (fd < 0) {
+ RNP_LOG("failed to create temporary file with template '%s'. Error %d.",
+ tmp.c_str(),
+ errno);
+ return RNP_ERROR_WRITE;
+ }
+ rnp_result_t res = init_fd_dest(dst, fd, tmp.c_str());
+ if (res) {
+ close(fd);
+ return res;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* now let's change some parameters to handle temporary file correctly */
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ param->overwrite = overwrite;
+ dst->finish = file_tmpdst_finish;
+ dst->close = file_tmpdst_close;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_stdout_dest(pgp_dest_t *dst)
+{
+ rnp_result_t res = init_fd_dest(dst, STDOUT_FILENO, "");
+ if (res) {
+ return res;
+ }
+ dst->type = PGP_STREAM_STDOUT;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+mem_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (!param) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* checking whether we need to realloc or discard extra bytes */
+ if (param->discard_overflow && (dst->writeb >= param->allocated)) {
+ return RNP_SUCCESS;
+ }
+ if (param->discard_overflow && (dst->writeb + len > param->allocated)) {
+ len = param->allocated - dst->writeb;
+ }
+
+ if (dst->writeb + len > param->allocated) {
+ if ((param->maxalloc > 0) && (dst->writeb + len > param->maxalloc)) {
+ RNP_LOG("attempt to alloc more then allowed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ /* round up to the page boundary and do it exponentially */
+ size_t alloc = ((dst->writeb + len) * 2 + 4095) / 4096 * 4096;
+ if ((param->maxalloc > 0) && (alloc > param->maxalloc)) {
+ alloc = param->maxalloc;
+ }
+
+ void *newalloc = param->secure ? calloc(1, alloc) : realloc(param->memory, alloc);
+ if (!newalloc) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (param->secure && param->memory) {
+ memcpy(newalloc, param->memory, dst->writeb);
+ secure_clear(param->memory, dst->writeb);
+ free(param->memory);
+ }
+ param->memory = newalloc;
+ param->allocated = alloc;
+ }
+
+ memcpy((uint8_t *) param->memory + dst->writeb, buf, len);
+ return RNP_SUCCESS;
+}
+
+static void
+mem_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ if (param->free) {
+ if (param->secure) {
+ secure_clear(param->memory, param->allocated);
+ }
+ free(param->memory);
+ }
+ free(param);
+ dst->param = NULL;
+}
+
+rnp_result_t
+init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len)
+{
+ pgp_dest_mem_param_t *param;
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_dest_mem_param_t *) dst->param;
+
+ param->maxalloc = len;
+ param->allocated = mem ? len : 0;
+ param->memory = mem;
+ param->free = !mem;
+ param->secure = false;
+
+ dst->write = mem_dst_write;
+ dst->close = mem_dst_close;
+ dst->type = PGP_STREAM_MEMORY;
+ dst->werr = RNP_SUCCESS;
+ dst->no_cache = true;
+
+ return RNP_SUCCESS;
+}
+
+void
+mem_dest_discard_overflow(pgp_dest_t *dst, bool discard)
+{
+ if (dst->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return;
+ }
+
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (param) {
+ param->discard_overflow = discard;
+ }
+}
+
+void *
+mem_dest_get_memory(pgp_dest_t *dst)
+{
+ if (dst->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return NULL;
+ }
+
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+
+ if (param) {
+ return param->memory;
+ }
+
+ return NULL;
+}
+
+void *
+mem_dest_own_memory(pgp_dest_t *dst)
+{
+ if (dst->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return NULL;
+ }
+
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+
+ if (!param) {
+ RNP_LOG("null param");
+ return NULL;
+ }
+
+ dst_finish(dst);
+
+ if (param->free) {
+ if (!dst->writeb) {
+ free(param->memory);
+ param->memory = NULL;
+ return param->memory;
+ }
+ /* it may be larger then required - let's truncate */
+ void *newalloc = realloc(param->memory, dst->writeb);
+ if (!newalloc) {
+ return NULL;
+ }
+ param->memory = newalloc;
+ param->allocated = dst->writeb;
+ param->free = false;
+ return param->memory;
+ }
+
+ /* in this case we should copy the memory */
+ void *res = malloc(dst->writeb);
+ if (res) {
+ memcpy(res, param->memory, dst->writeb);
+ }
+ return res;
+}
+
+void
+mem_dest_secure_memory(pgp_dest_t *dst, bool secure)
+{
+ if (!dst || (dst->type != PGP_STREAM_MEMORY)) {
+ RNP_LOG("wrong function call");
+ return;
+ }
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (param) {
+ param->secure = secure;
+ }
+}
+
+static rnp_result_t
+null_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ return RNP_SUCCESS;
+}
+
+static void
+null_dst_close(pgp_dest_t *dst, bool discard)
+{
+ ;
+}
+
+rnp_result_t
+init_null_dest(pgp_dest_t *dst)
+{
+ dst->param = NULL;
+ dst->write = null_dst_write;
+ dst->close = null_dst_close;
+ dst->type = PGP_STREAM_NULL;
+ dst->writeb = 0;
+ dst->clen = 0;
+ dst->werr = RNP_SUCCESS;
+ dst->no_cache = true;
+
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit)
+{
+ const size_t bufsize = PGP_INPUT_CACHE_SIZE;
+ uint8_t * readbuf = (uint8_t *) malloc(bufsize);
+ if (!readbuf) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t res = RNP_SUCCESS;
+ try {
+ size_t read;
+ uint64_t totalread = 0;
+
+ while (!src->eof) {
+ if (!src_read(src, readbuf, bufsize, &read)) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (!read) {
+ continue;
+ }
+ totalread += read;
+ if (limit && totalread > limit) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (dst) {
+ dst_write(dst, readbuf, read);
+ if (dst->werr) {
+ RNP_LOG("failed to output data");
+ res = RNP_ERROR_WRITE;
+ break;
+ }
+ }
+ }
+ } catch (...) {
+ free(readbuf);
+ throw;
+ }
+ free(readbuf);
+ if (res || !dst) {
+ return res;
+ }
+ dst_flush(dst);
+ return dst->werr;
+}