aboutsummaryrefslogtreecommitdiff
path: root/snag/src/download_db.c
blob: e0aa85e11de8c7a5917c911b5681c0796a8d9093 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "download_db.h"

#include "util.h"

#include <curl/curl.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

// note: fixed location of download database for now
const char download_db_path[] = "./var/snag/downloads";

// libcrpyto context
EVP_MD_CTX *mdctx = NULL;
const EVP_MD *md = NULL;

// curl context
CURL *curl = NULL;

static size_t download_write_callback(char* ptr, size_t size, size_t nmemb, void* usrdata) {
	// write the curl data to the file in usr data
	size_t written = fwrite(ptr, size, nmemb, (FILE *)usrdata);

	// update the hash with the new data
    EVP_DigestUpdate(mdctx, ptr, written);

	return written;
}

bool package_get_download(package_info_t* pkg, package_download_t* download) {
    memset(download, 0, sizeof(*download));

	// construct path of download
	// note(jqj): extension is fixed to tar gz until the switch out download
	//            attrs for the swenu api download command which will specify
	//            more information
	char* download_path = vastrcat(download_db_path, "/", pkg->attrs.id, "-", pkg->attrs.version, ".tar.gz");

	// return download if it already exists in database
    if (access(download_path, F_OK) == 0) {
		// todo(jqj): verify checksum anyway
		download->download_path = download_path;
		return true;
    } else {
		print("Downloading %s\n", pkg->attrs.source_url);
	}

	// initialize curl if it isn't already
	if (curl == NULL) {
		CURLcode result = curl_global_init(CURL_GLOBAL_ALL);
		if (result != CURLE_OK) die("failed to initialize curl: %s", curl_easy_strerror(result));
		curl = curl_easy_init(); // note(jqj): for now we reuse one easy curl object
		if (curl == NULL) die("failed to initialize easy curl");

		/* come back to this if we have init and deinit */
		/* curl_easy_cleanup(curl); */
		/* curl_global_cleanup(); */
	}

	// initialize libcrypto context if it isn't already
	if (mdctx == NULL) {
		mdctx = EVP_MD_CTX_new();
		md = EVP_sha256();

		/* come back to this if we have init and deinit */
		/* EVP_MD_CTX_free(mdctx); */
	}

	// initialize up sha256 hash
    EVP_DigestInit_ex(mdctx, md, NULL);

	// open file for writing
	FILE* download_file = fopen(download_path, "wb");
	if (download_file == NULL) die("failed open download file for writing (%s): %s", download_path, strerror(errno));

	// make curl request with url and output file
	// callback writes to the file and feeds the hash
	curl_easy_setopt(curl, CURLOPT_URL, pkg->attrs.source_url);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_write_callback);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, download_file);
	CURLcode result = curl_easy_perform(curl);
	fclose(download_file);
    if(result != CURLE_OK) {
		// todo(jqj): handle better, maybe try again
		print("failed to download package with curl: %s", curl_easy_strerror(result));
		goto fail;
	}

	// get sha256 hash
    unsigned char hash[SHA256_DIGEST_LENGTH];
	unsigned int hash_len;
    EVP_DigestFinal_ex(mdctx, hash, &hash_len);

	// make hexadecimal string of sha256 hash
	char hash_hex[SHA256_DIGEST_LENGTH * 2 + 1];
	for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        sprintf(hash_hex + i*2,"%02x", hash[i]);
    }
	hash_hex[SHA256_DIGEST_LENGTH * 2] = '\0';

	// confirm that checksum matches
	assert(pkg->attrs.source_checksum != NULL); 
	if (strcmp(pkg->attrs.source_checksum, hash_hex) == 0) {
		print("checksum verified for download: %s\n", hash_hex);
		download->download_path = download_path;
		return true;
	} else {
		print("FAIL: bad checksum for download: %s\n", hash_hex);
		goto fail;
	}

 fail:
    if (access(download_path, F_OK) == 0) remove(download_path);
	free(download_path);
	return false;
}