26
#include <sys/types.h>
29
27
#include <sys/stat.h>
30
28
#include <unistd.h>
32
30
#include <string.h>
34
33
#include "favicon.h"
35
34
#include "support.h"
37
36
#include "common.h"
38
37
#include "update.h"
39
#include "net/netio.h"
46
/* The maximum number of icons we'll try to handle in any one file */
49
/* Header of a single icon image in an icon file */
50
typedef struct IconDirectoryEntry {
52
unsigned char bHeight;
53
unsigned char bColorCount;
54
unsigned char bReserved;
55
unsigned short wPlanes;
56
unsigned short wBitCount;
57
unsigned long dwBytesInRes;
58
unsigned long dwImageOffset;
61
/* Header of an icon file */
62
typedef struct ICONDIR {
63
unsigned short idReserved;
64
unsigned short idType;
65
unsigned short idCount;
66
/* ICONDIRENTRY idEntries[1];*/
67
ICONDIRENTRY idEntries[MAXICONS];
70
/* Bitmap header - this is on the images themselves */
71
typedef struct tagBITMAPINFOHEADER{
75
unsigned short biPlanes;
76
unsigned short biBitCount;
77
unsigned long biCompression;
78
unsigned long biSizeImage;
81
unsigned long biClrUsed;
82
unsigned long biClrImportant;
85
/* Magic number for an icon file */
86
/* This is the the idReserved and idType fields of the header */
87
static char ico_magic_number[4] = {0, 0, 1, 0};
89
static int do_verbose = 0;
91
/* void convert __P((void)); */
93
/* For keeping track of which icon image we're on */
94
static unsigned short whichimage = 0;
100
#define XPM_HEAD "/* XPM */\n\
101
static char *pixmap[] = {\n\
102
/* width height ncols cpp */\n\
103
\"%d %d %d 2\",\n /* Colors */\n"
105
/* The smallest number of double words containing c bytes */
106
#define DWORD_ALIGN_BYTES(b) (((b) / 4) + (((b) % 4 > 0) ? 1 : 0))
108
/* The smallest number of double words containing c bits */
109
#define DWORD_ALIGN_BITS(b) (((b) / 32) + (((b) % 32 > 0) ? 1 : 0))
111
/* The different possible number of colors */
114
/*#define NCOLORS (color_level == LOW_COLOR ? 16 : 256)*/
115
#define NCOLORS ncolors
117
/* The color index of a pixel in the icon colormap */
118
#define PIXEL_INDEX(x,y) \
119
(color_level == LOW_COLOR \
121
? (image [bytes_per_line * (height - 1 - (y)) / 2 + (x) / 2] & 0xF0) >> 4 \
122
: image [bytes_per_line * (height - 1 - (y)) / 2 + (x) / 2] & 0x0F) \
123
: image [bytes_per_line * (height - 1 - (y)) + (x)])
125
/* The color index of a pixel in the (reduced) pixmap colormap */
126
#define PIXEL_REDUCED_INDEX(x,y) \
127
(reduced_colormap_index [PIXEL_INDEX((x),(y))])
129
/* The RGB value of a colormap entry */
130
#define INDEX_VALUE(index,c) (colormap[(c) + (index) * 4])
131
#define INDEX_R_VALUE(index) (INDEX_VALUE((index), 2))
132
#define INDEX_G_VALUE(index) (INDEX_VALUE((index), 1))
133
#define INDEX_B_VALUE(index) (INDEX_VALUE((index), 0))
135
/* The transparency of a pixel */
136
#define MASK_BYTE(x,y) (bytes_per_mask_line * (height - 1 - (y)) + (x) / 8)
137
#define MASK_BIT(x,y) (7 - (x) % 8)
138
#define IS_TRANSPARENT(x,y) \
139
(mask[MASK_BYTE((x),(y))] & (1 << MASK_BIT((x),(y))))
141
static FILE *xpm_stream;
143
/* Values read from icon file */
144
static ICONHEADER iconheader;
145
static BITMAPINFOHEADER bitmapinfoheader;
146
/*static unsigned char magic_number [sizeof(ico_magic_number)];*/
147
static unsigned char *magic_number;
148
static unsigned char color_level;
149
static unsigned char colormap[256 * 4];
150
static unsigned char image[256 * 256];
151
static unsigned char mask[256 * 32];
153
/* Different variables computed from the read ones */
154
static unsigned ncolors;
155
static unsigned char width;
156
static unsigned char height;
157
static unsigned char bytes_per_line;
158
static unsigned char bytes_per_mask_line;
159
static int image_length;
160
static int mask_length;
163
/* Variables for the reduced colormap */
164
static int color_used[256];
165
static int nb_color_used;
166
static unsigned char reduced_colormap_index [256];
168
/* Does the icon have transparent pixels ? */
169
static int have_transparent_pixels;
172
/* function reads the data of a MS Windows .ICO file referenced by
173
icondata with length datalen, converts the image into XPM
174
format and saves the result in the file outputfile. If the
175
conversion is successful TRUE is returned. */
177
convert_favicon_to_XPM(gchar *outputfile, unsigned char *icondata, int datalen)
181
/* Check the magic number & header */
182
/*fread (magic_number, 1, sizeof(ico_magic_number), ico_stream);*/
183
//fread (&iconheader, 1, 6/*sizeof(unsigned short)*3*/, ico_stream);
184
memcpy(&iconheader, icondata, 6);
186
magic_number = (unsigned char *)&iconheader;
187
for (i = 0 ; i < sizeof(ico_magic_number) ; i ++)
189
if (magic_number [i] != ico_magic_number [i])
191
g_warning("favicon.ico data is not a recognized icon file.\n");
196
/* Output some stats */
198
g_print("favicon.ico data contains %d icon images\n", GUINT16_FROM_LE(iconheader.idCount));
200
/* Read in the rest of the icon directory entries */
201
//fread(&iconheader.idEntries[0],iconheader.idCount,
202
// /*sizeof(ICONDIRENTRY)*/16,ico_stream);
203
memcpy(&iconheader.idEntries[0], icondata + offset, GUINT16_FROM_LE(iconheader.idCount)*16);
204
offset += GUINT16_FROM_LE(iconheader.idCount)*16;
206
/* Cycle through each icon image */
207
for(whichimage = 0; whichimage < GUINT16_FROM_LE(iconheader.idCount); whichimage++) {
210
g_print("(%d) identry: %d %d %d %d %d %d %d %d\n",
212
(int)iconheader.idEntries[whichimage].bWidth,
213
(int)iconheader.idEntries[whichimage].bHeight,
214
(int)iconheader.idEntries[whichimage].bColorCount,
215
(int)iconheader.idEntries[whichimage].bReserved,
216
(int)GUINT16_FROM_LE(iconheader.idEntries[whichimage].wPlanes),
217
(int)GUINT16_FROM_LE(iconheader.idEntries[whichimage].wBitCount),
218
(int)GULONG_FROM_LE(iconheader.idEntries[whichimage].dwBytesInRes),
219
(int)GULONG_FROM_LE(iconheader.idEntries[whichimage].dwImageOffset));
223
/* Update the output file name */
226
/* Read the icon size */
227
width = iconheader.idEntries[whichimage].bWidth;
228
height = iconheader.idEntries[whichimage].bWidth;
230
/* Determine the number of bytes defining a line of the icon. */
231
bytes_per_line = 4 * DWORD_ALIGN_BYTES (width);
232
bytes_per_mask_line = 4 * DWORD_ALIGN_BITS (width);
234
/* Let's surf on over to the bitmap image & read the BMIH. */
235
//fseek (ico_stream, GULONG_FROM_LEiconheader.idEntries[whichimage].dwImageOffset),SEEK_SET);
236
//fread (&bitmapinfoheader, 1, sizeof(bitmapinfoheader), ico_stream);
237
offset = GULONG_FROM_LE(iconheader.idEntries[whichimage].dwImageOffset);
238
memcpy(&bitmapinfoheader, icondata + offset, sizeof(bitmapinfoheader));
242
g_print("(%d) bmih: %d %d %d %d %d %d %d %d %d %d %d\n",
244
(int)GULONG_FROM_LE(bitmapinfoheader.biSize),
245
(int)GLONG_FROM_LE(bitmapinfoheader.biWidth),
246
(int)GLONG_FROM_LE(bitmapinfoheader.biHeight),
247
(int)GUINT16_FROM_LE(bitmapinfoheader.biPlanes),
248
(int)GUINT16_FROM_LE(bitmapinfoheader.biBitCount),
249
(int)GULONG_FROM_LE(bitmapinfoheader.biCompression),
250
(int)GULONG_FROM_LE(bitmapinfoheader.biSizeImage),
251
(int)GLONG_FROM_LE(bitmapinfoheader.biXPelsPerMeter),
252
(int)GLONG_FROM_LE(bitmapinfoheader.biYPelsPerMeter),
253
(int)GULONG_FROM_LE(bitmapinfoheader.biClrUsed),
254
(int)GULONG_FROM_LE(bitmapinfoheader.biClrImportant));
257
/* Read the number of colors.
258
* TODO: add support for monochrome, 24-bit icons
260
switch(GUINT16_FROM_LE(bitmapinfoheader.biPlanes)
261
* GUINT16_FROM_LE(bitmapinfoheader.biBitCount)) {
262
case 4: /* 2^4 = 14 */ color_level = LOW_COLOR; break;
263
case 8: /* 2^8 = 256 */ color_level = HIGH_COLOR; break;
265
g_warning("Unsupported number of colors in favicon.ico image! Skipping. (%d planes, %d bpp)\n",
266
(int)GUINT16_FROM_LE(bitmapinfoheader.biPlanes),
267
(int)GUINT16_FROM_LE(bitmapinfoheader.biBitCount));
271
/* Read the colormap */
272
if(GULONG_FROM_LE(bitmapinfoheader.biClrImportant) != 0)
273
ncolors = GULONG_FROM_LE(bitmapinfoheader.biClrImportant);
274
else if(GULONG_FROM_LE(bitmapinfoheader.biClrUsed) != 0)
275
ncolors = GULONG_FROM_LE(bitmapinfoheader.biClrUsed);
277
ncolors = 1 << (GUINT16_FROM_LE(bitmapinfoheader.biPlanes)*GUINT16_FROM_LE(bitmapinfoheader.biBitCount));
279
/*fseek (ico_stream, iconheader.idEntries[whichimage].dwImageOffset
280
+ bitmapinfoheader.biSize, SEEK_SET);
281
fread (colormap, 1,ncolors * 4, ico_stream);*/
282
offset += GULONG_FROM_LE(bitmapinfoheader.biSize);
283
memcpy(&colormap, icondata + offset, ncolors*4);
287
if (color_level == LOW_COLOR)
288
image_length = bytes_per_line * height / 2;
290
image_length = bytes_per_line * height;
291
/*fread (image, 1, image_length, ico_stream);*/
292
memcpy(&image, icondata + offset, image_length);
293
offset += image_length;
296
mask_length = bytes_per_mask_line * height;
297
/*fread (mask, 1, mask_length, ico_stream);*/
298
memcpy(&mask, icondata + offset, mask_length);
299
offset += mask_length;
301
/* Read error / prematured EOF occured ? */
302
if((mask + mask_length) > (icondata + datalen))
303
g_warning("Premature end of favicon.ico data.\n");
304
/*if (ferror (ico_stream))
305
fprintf (stderr, "%s: %s: Read error.\n",
306
program_name, input_file);*/
307
/*if (feof (ico_stream) || ferror (ico_stream))
313
/* Reduce the number of colors */
314
for (i = 0 ; i < ncolors ; i++)
317
for (y = 0 ; y <height ; y++)
318
for (x = 0 ; x < width ; x++)
319
color_used [PIXEL_INDEX(x,y)] = 1;
322
for (i = 0 ; i < ncolors ; i++)
324
reduced_colormap_index[i] = nb_color_used ++;
326
/* Check for at least one transparent pixel */
327
have_transparent_pixels = 0;
328
for (y=0; y<height && have_transparent_pixels == 0 ; y++)
329
for (x=0; x<width; x++)
330
if (IS_TRANSPARENT(x,y))
332
have_transparent_pixels = 1;
337
* Write the pixmap file
339
if ((xpm_stream = fopen (outputfile, "w")) == NULL)
341
perror (outputfile); // FIXME: use glib
345
/* Write the header */
346
fprintf (xpm_stream, XPM_HEAD,
348
nb_color_used + have_transparent_pixels);
350
/* Write the colors */
352
for (i=0; i<NCOLORS; i++)
356
fprintf (xpm_stream, " \"%.2X c #%.2X%.2X%.2X\",\n", x++,
357
INDEX_R_VALUE(i), INDEX_G_VALUE(i), INDEX_B_VALUE(i));
360
if (have_transparent_pixels)
361
fprintf (xpm_stream, " \".. s None c None\",\n");
363
/* Write the image */
364
for (y=0; y<height; y++)
366
fprintf (xpm_stream, " \"");
367
for (x=0; x<width; x++)
369
if (IS_TRANSPARENT(x,y))
370
fprintf (xpm_stream, "..");
372
fprintf (xpm_stream, "%.2X", PIXEL_REDUCED_INDEX(x,y));
374
fprintf (xpm_stream, "\",\n");
376
fprintf (xpm_stream, "};\n");
378
/* Write error occured ? */
379
if (ferror (xpm_stream))
380
g_warning("Error writing %s.\n", outputfile);
384
/* Write info about xpm file written */
386
g_print("Wrote %s %dx%dx%d (%d)\n",
387
outputfile, width, height,
388
nb_color_used, NCOLORS);
390
/* we intentionally return after the first retrieved pixmap */
396
/* Liferea specific wrapper functions */
40
#include "ui_feedlist.h"
41
#include "ui_mainwindow.h"
398
44
void favicon_load(feedPtr fp) {
400
48
GdkPixbuf *pixbuf;
401
GError *error = NULL;
403
51
/* try to load a saved favicon */
404
filename = common_create_cache_filename("cache" G_DIR_SEPARATOR_S "favicons",fp->id, "xpm");
406
if(g_file_test(filename, G_FILE_TEST_EXISTS)) {
407
/* remove path, because create_pixbuf allows no absolute pathnames */
408
pixbuf = gdk_pixbuf_new_from_file (filename, &error);
52
filename = common_create_cache_filename("cache" G_DIR_SEPARATOR_S "favicons", feed_get_id(fp), "png");
54
if(0 == stat((const char*)filename, &statinfo)) {
55
pixbuf = gdk_pixbuf_new_from_file(filename, &error);
58
g_object_unref(fp->icon);
410
59
fp->icon = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR);
411
60
g_object_unref(pixbuf);
412
61
} else { /* Error */
413
fprintf (stderr, "Failed to load pixbuf file: %s: %s\n",
414
filename, error->message);
415
g_error_free (error);
62
fprintf(stderr, "Failed to load pixbuf file: %s: %s\n",
63
filename, error->message);
67
/* check creation date and update favicon if older than one month */
68
g_get_current_time(&now);
69
if(now.tv_sec > statinfo.st_mtime + 60*60*24*31) {
70
debug1(DEBUG_UPDATE, "updating favicon %s\n", filename);
422
77
void favicon_remove(feedPtr fp) {
425
80
/* try to load a saved favicon */
426
filename = common_create_cache_filename( "cache" G_DIR_SEPARATOR_S "favicons", fp->id, "xpm");
81
filename = common_create_cache_filename( "cache" G_DIR_SEPARATOR_S "favicons", feed_get_id(fp), "png");
427
82
if(g_file_test(filename, G_FILE_TEST_EXISTS)) {
428
if(0 != unlink(filename)) {
429
g_warning(_("Could not delete icon file %s! Please remove manually!"), filename);
83
if(0 != unlink(filename))
84
/* What can we do? The file probably doesn't exist. Or permissions are wrong. Oh well.... */;
435
void favicon_download(feedPtr fp) {
436
unsigned char *icodata;
90
* This code tries to download a series of files. If there are no
91
* favicons, this will make four downloads, two of which will be 404
92
* errors. Hopefully this will not cause any webservers pain because
93
* this code should be run only once a month per feed.
95
* Flag states: (stored in request->flags)
97
* 0 <-- downloading HTML of the feed url
98
* 1 <-- downloading favicon from the feed url HTML
99
* 2 <-- downloading HTML of root of webserver
100
* 3 <-- downloading favicon from the root HTML
101
* 4 <-- downloading favicon from directory of RSS feed
102
* 5 <-- downloading favicon from root of webserver
105
static void favicon_download_request_favicon_cb(struct request *request);
106
static void favicon_download_html(feedPtr fp, int phase);
108
static void favicon_download_5(feedPtr fp) {
109
gchar *baseurl, *tmp;
110
struct request *request;
112
baseurl = g_strdup(feed_get_source(fp));
113
if(NULL != (tmp = strstr(baseurl, "://"))) {
115
if(NULL != (tmp = strchr(tmp, '/'))) {
117
request = download_request_new(NULL);
118
request->source = g_strdup_printf("%s/favicon.ico", baseurl);
120
request->callback = &favicon_download_request_favicon_cb;
121
request->user_data = fp;
123
fp->otherRequests = g_slist_append(fp->otherRequests, request);
125
debug1(DEBUG_UPDATE, "trying to download server root favicon.ico for \"%s\"\n", request->source);
127
download_queue(request);
133
static void favicon_download_4(feedPtr fp) {
134
gchar *baseurl, *tmp;
135
struct request *request;
137
baseurl = g_strdup(feed_get_source(fp));
138
if(NULL != (tmp = strstr(baseurl, "://"))) {
140
if(NULL != (tmp = strrchr(tmp, '/'))) {
143
request = download_request_new(NULL);
144
request->source = g_strdup_printf("%s/favicon.ico", baseurl);
145
request->callback = &favicon_download_request_favicon_cb;
146
request->user_data = fp;
148
fp->otherRequests = g_slist_append(fp->otherRequests, request);
150
debug1(DEBUG_UPDATE, "trying to download favicon.ico for \"%s\"\n", request->source);
152
download_queue(request);
158
static void favicon_download_request_favicon_cb(struct request *request) {
159
feedPtr fp = (feedPtr)request->user_data;
161
gboolean success = FALSE;
163
debug2(DEBUG_UPDATE, "icon download processing (%s, %d bytes)", request->source, request->size);
164
fp->otherRequests = g_slist_remove(fp->otherRequests, request);
166
if(NULL != request->data && request->size > 0) {
167
GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
169
tmp = common_create_cache_filename("cache" G_DIR_SEPARATOR_S "favicons", feed_get_id(fp), "png");
171
if(gdk_pixbuf_loader_write(loader, request->data, request->size, NULL)) {
172
if(NULL != (pixbuf = gdk_pixbuf_loader_get_pixbuf(loader))) {
173
debug1(DEBUG_UPDATE, "saving icon as %s", tmp);
174
if(FALSE == (gdk_pixbuf_save(pixbuf, tmp, "png", NULL, NULL))) {
175
g_warning("favicon saving error!");
181
gdk_pixbuf_loader_close(loader, NULL);
182
g_object_unref(loader);
188
if(request->flags == 1)
189
favicon_download_html(fp, 2);
190
else if(request->flags == 3) {
191
favicon_download_4(fp);
192
} else if(request->flags == 4) {
193
favicon_download_5(fp);
198
static void favicon_download_html_request_cb(struct request *request) {
200
struct request *request2 = NULL;
201
feedPtr fp = (feedPtr)request->user_data;
203
fp->otherRequests = g_slist_remove(fp->otherRequests, request);
205
if (request->size > 0 && request->data != NULL) {
206
iconUri = html_discover_favicon(request->data, request->source);
207
if (iconUri != NULL) {
208
request2 = download_request_new(NULL);
209
request2->source = iconUri;
210
request2->callback = &favicon_download_request_favicon_cb;
211
request2->user_data = fp;
213
fp->otherRequests = g_slist_append(fp->otherRequests, request2);
214
download_queue(request2);
217
if (request2 == NULL) {
218
if (request->flags == 0)
219
favicon_download_html((feedPtr)request->user_data, 2);
220
else /* flags == 2 */
221
favicon_download_4((feedPtr)fp);
225
static void favicon_download_html(feedPtr fp, int phase) {
439
struct feed_request *request;
228
struct request *request;
441
230
/* try to download favicon */
442
baseurl = g_strdup(fp->source);
443
if(NULL != (tmp = strstr(baseurl, "://"))) {
445
if(NULL != (tmp = strchr(tmp, '/'))) {
448
request = update_request_new(NULL);
449
request->feedurl = g_strdup_printf("%s/favicon.ico", baseurl);
450
debug1(DEBUG_UPDATE, "trying to download favicon.ico for \"%s\"\n", request->feedurl);
451
icodata = downloadURL(request);
452
update_request_free(request);
454
if(NULL != icodata) {
455
tmp = common_create_cache_filename("cache" G_DIR_SEPARATOR_S "favicons", fp->id, "xpm");
456
convert_favicon_to_XPM(tmp, icodata, 10000000);
232
htmlurl = g_strdup(feed_get_html_url(fp));
234
htmlurl = g_strdup(feed_get_source(fp));
235
if(NULL != (tmp = strstr(htmlurl, "://"))) {
237
/* first we try to download a favicon inside the current web path
238
if the download fails the callback will try to strip parts of
239
the URL to download a root favicon. */
240
if(NULL != (tmp = strrchr(tmp, '/'))) {
246
request = download_request_new(NULL);
247
request->source = htmlurl;
248
request->callback = &favicon_download_html_request_cb;
249
request->user_data = fp;
250
request->flags = phase;
251
fp->otherRequests = g_slist_append(fp->otherRequests, request);
252
download_queue(request);
254
debug_exit("favicon_download");
257
void favicon_download(feedPtr fp) {
259
if(FST_VFOLDER == feed_get_type(fp))
262
debug_enter("favicon_download");
263
debug1(DEBUG_UPDATE, "trying to download favicon.ico for \"%s\"\n", feed_get_title(fp));
265
ui_mainwindow_set_status_bar(_("Updating feed icon for \"%s\""),
268
g_get_current_time(&fp->lastFaviconPoll);
270
if(feed_get_html_url(fp) != NULL) {
271
favicon_download_html(fp, 0);
273
favicon_download_html(fp, 2);
276
debug_exit("favicon_download");