1 #include <string.h>
2 #include <stdbool.h>
3 #include <stdlib.h>
4 #include <ctype.h>
5 
6 #include <glib.h>
7 #include <gdk-pixbuf/gdk-pixbuf.h>
8 
9 #ifdef _WIN32
10 # include <windows.h>
11 #endif
12 
13 #include <curl/curl.h>
14 
15 #include "memfile.h"
16 #include "from_url.h"
17 
18 #define REQUEST_TIMEOUT (5)
19 
20 CURLcode
memfile_from_url(const memfile_from_url_info info)21 memfile_from_url(const memfile_from_url_info info) {
22   CURL* curl = curl_easy_init();
23   if (!curl) return CURLE_FAILED_INIT;
24 
25   MEMFILE* body = memfopen();
26   long code = 0;
27   double csize = -1;
28   char* ctype = NULL;
29 
30   curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
31   curl_easy_setopt(curl, CURLOPT_URL, info.url);
32   curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, REQUEST_TIMEOUT);
33   curl_easy_setopt(curl, CURLOPT_TIMEOUT, REQUEST_TIMEOUT);
34   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, info.body_writer);
35   curl_easy_setopt(curl, CURLOPT_WRITEDATA, body);
36   curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
37   const CURLcode res = curl_easy_perform(curl);
38 
39   if (res == CURLE_OK) {
40     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
41 
42     if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &csize) != CURLE_OK)
43       csize = -1;
44     curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ctype);
45   }
46 
47   if (info.code)  *info.code  = code;
48   if (info.csize) *info.csize = csize;
49   if (info.ctype) *info.ctype = ctype ? strdup(ctype) : NULL;
50   if (info.body)  *info.body  = memfrelease(&body);
51   memfclose(body);
52 
53   curl_easy_cleanup(curl);
54 
55   return res;
56 }
57 
58 // Some error happened only if returns true.
59 static bool
gerror_set_or_free(GError ** dest,GError * val)60 gerror_set_or_free(GError** dest, GError* val) {
61   if (!val) return false;
62 
63   if (dest) *dest = val;
64   else g_error_free(val);
65   return true;
66 }
67 
68 // FIXME: More refactor this function.
69 static GdkPixbuf*
pixbuf_from_url_impl(const char * ctype,const MEMFILE * raw,GError ** error)70 pixbuf_from_url_impl(const char* ctype, const MEMFILE* raw, GError** error) {
71   GdkPixbuf* pixbuf = NULL;
72 #ifdef _WIN32
73   if (ctype && (!strcmp(ctype, "image/jpeg") || !strcmp(ctype, "image/gif"))) {
74     char temp_path[MAX_PATH];
75     char temp_filename[MAX_PATH];
76     GetTempPath(sizeof(temp_path), temp_path);
77     GetTempFileName(temp_path, "growl-for-linux-", 0, temp_filename);
78     FILE* const fp = fopen(temp_filename, "wb");
79     if (fp) {
80       fwrite(memfdata(raw), memfsize(raw), 1, fp);
81       fclose(fp);
82     }
83     pixbuf = gdk_pixbuf_new_from_file(temp_filename, NULL);
84     DeleteFile(temp_filename);
85   } else
86 #endif
87   {
88     GError* _error = NULL;
89     GdkPixbufLoader* const loader =
90       ctype ? gdk_pixbuf_loader_new_with_mime_type(ctype, &_error)
91             : gdk_pixbuf_loader_new();
92     if (!gerror_set_or_free(error, _error)) {
93       if (gdk_pixbuf_loader_write(loader, (const guchar*) memfcdata(raw), memfsize(raw), &_error))
94         pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
95       else
96         gerror_set_or_free(error, _error);
97 
98       gdk_pixbuf_loader_close(loader, NULL);
99     }
100   }
101   return pixbuf;
102 }
103 
104 GdkPixbuf*
pixbuf_from_url(const char * url,GError ** error)105 pixbuf_from_url(const char* url, GError** error) {
106   if (!url) return NULL;
107   if (!strncmp(url, "x-growl-resource://", 19)) {
108     const gchar* const confdir = (const gchar*) g_get_user_config_dir();
109     gchar* const resourcedir = g_build_path(G_DIR_SEPARATOR_S, confdir, "gol", "resource", NULL);
110     const gchar* const newurl = g_build_filename(resourcedir, url + 19, NULL);
111     GdkPixbuf* pixbuf = pixbuf_from_url_as_file(newurl, error);
112     g_free(resourcedir);
113 	return pixbuf;
114   }
115 
116   MEMFILE* mbody;
117   long code;
118   double csize;
119   char* ctype;
120   const CURLcode res = memfile_from_url((memfile_from_url_info){
121     .url         = url,
122     .body        = &mbody,
123     .body_writer = memfwrite,
124     .code        = &code,
125     .csize       = &csize,
126     .ctype       = &ctype,
127   });
128   if (res != CURLE_OK || code != 200 || !mbody) {
129     if (error) *error = g_error_new_literal(G_FILE_ERROR, res, curl_easy_strerror(res));
130     free(ctype);
131     memfclose(mbody);
132     return NULL;
133   }
134 
135   memfresize(mbody, csize >= 0 ? (size_t) csize : memfsize(mbody));
136 
137   GdkPixbuf* const pixbuf = pixbuf_from_url_impl(ctype, mbody, error);
138 
139   free(ctype);
140   memfclose(mbody);
141 
142   return pixbuf;
143 }
144 
145 GdkPixbuf*
pixbuf_from_url_as_file(const char * url,GError ** error)146 pixbuf_from_url_as_file(const char* url, GError** error) {
147   if (!url) return NULL;
148   gchar* newurl;
149   if (!strncmp(url, "x-growl-resource://", 19)) {
150     const gchar* const confdir = (const gchar*) g_get_user_config_dir();
151     gchar* const resourcedir = g_build_path(G_DIR_SEPARATOR_S, confdir, "gol", "resource", NULL);
152     newurl = g_build_filename(resourcedir, url + 19, NULL);
153     g_free(resourcedir);
154   } else
155     newurl = g_filename_from_uri(url, NULL, NULL);
156   GError* _error = NULL;
157   GdkPixbuf* const pixbuf = gdk_pixbuf_new_from_file(newurl ? newurl : url, &_error);
158   if (!pixbuf) gerror_set_or_free(error, _error);
159   g_free(newurl);
160   return pixbuf;
161 }
162 
163