1 
2 /*
3  * Osmo - a handy personal organizer
4  *
5  * Copyright (C) 2015 Tomasz Mąka <pasp@users.sourceforge.net>
6  *               2015 Piotr Mąka <silloz@users.sourceforge.net>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21  */
22 
23 #include <stdlib.h>
24 
25 #include "vcf.h"
26 #include "gui.h"
27 #include <stdlib.h>
28 #include <glib.h>
29 #include <string.h>
30 
31 #define CRLF "\r\n"
32 #define CRLF_SIZE 2
33 #define DELIM_COMPONENT ';'
34 #define DELIM_LIST ','
35 
36 #define MAX_LINE_SIZE 75
37 #define MAX_FOLDED_LINE_SIZE 74
38 #define LINE_FOLD CRLF " "
39 #define LINE_FOLD_SIZE 3
40 #define MAX_ESCAPE_SIZE 2
41 
42 #define BEGIN "BEGIN:VCARD"
43 #define END "END:VCARD"
44 #define VERSION_40 "VERSION:4.0"
45 
46 #define FN "FN"
47 #define N "N"
48 #define NICKNAME "NICKNAME"
49 #define BDAY "BDAY"
50 #define ADR "ADR"
51 #define ORG "ORG"
52 #define TEL "TEL"
53 #define EMAIL "EMAIL"
54 #define URL "URL"
55 #define IMPP "IMPP"
56 #define NOTE "NOTE"
57 
58 #define TYPE_PARAM "TYPE"
59 #define PREF_PARAM "PREF"
60 
61 #define TEL_URI_SCHEME "tel"
62 
63 struct vcf_writer {
64     vcf_writer_callback *callback;
65     void *user_data;
66 };
67 
68 static void write_string(vcf_writer *writer, gchar const *content);
69 static void write_value(vcf_writer *writer, gchar const *property_name, gchar const *value);
70 static void write_multivalue(vcf_writer *writer, gchar const *property_name, gchar separator, gint component_count, ...);
71 static gchar *format_date(guint32 julian_day);
72 
73 /*------------------------------------------------------------------------------*/
74 
75 vcf_writer *
vcf_create_writer(vcf_writer_callback * callback,void * user_data)76 vcf_create_writer(vcf_writer_callback *callback, void *user_data) {
77       vcf_writer *writer = g_new(vcf_writer, 1);
78       if(!writer) {
79           return NULL;
80       }
81       writer->callback = callback;
82       writer->user_data = user_data;
83 
84       return writer;
85 }
86 
87 void
vcf_close_writer(vcf_writer * writer)88 vcf_close_writer(vcf_writer *writer) {
89     g_free(writer);
90 }
91 
92 
93 void
vcf_write_begin(vcf_writer * writer)94 vcf_write_begin(vcf_writer *writer) {
95     write_string(writer, BEGIN);
96     write_string(writer, VERSION_40);
97 }
98 
99 void
vcf_write_end(vcf_writer * writer)100 vcf_write_end(vcf_writer *writer) {
101     write_string(writer, END);
102 }
103 
104 void
vcf_write_FN(vcf_writer * writer,char const * first_name,char const * last_name)105 vcf_write_FN(vcf_writer *writer, char const *first_name, char const *last_name) {
106     gchar *buffer;
107 
108     if(first_name && last_name) {
109         buffer = g_strdup_printf("%s %s", first_name, last_name);
110     } else  if(first_name) {
111         buffer = g_strdup(first_name);
112     }else {
113         buffer = g_strdup(last_name);
114     }
115 
116     write_value(writer, FN, buffer);
117 
118     g_free(buffer);
119 }
120 
121 void
vcf_write_N(vcf_writer * writer,char const * last_name,char const * first_name,char const * second_name)122 vcf_write_N(vcf_writer *writer, char const *last_name, char const *first_name, char const *second_name) {
123     write_multivalue(writer, N, DELIM_COMPONENT, 5, last_name, first_name, second_name, NULL, NULL);
124 }
125 
126 void
vcf_write_NICKNAME(vcf_writer * writer,char const * nick_name)127 vcf_write_NICKNAME(vcf_writer *writer, char const *nick_name) {
128     write_multivalue(writer, NICKNAME, DELIM_LIST, 1, nick_name);
129 }
130 
131 void
vcf_write_BDAY(vcf_writer * writer,guint32 julian_day)132 vcf_write_BDAY(vcf_writer *writer, guint32 julian_day) {
133     gchar *text = format_date(julian_day);
134     g_return_if_fail(text);
135 
136     write_value(writer, BDAY, text);
137     g_free(text);
138 }
139 
140 void
vcf_write_ADR(vcf_writer * writer,char const * type,char const * address,char const * city,char const * state,char const * post_code,char const * country)141 vcf_write_ADR(vcf_writer *writer, char const *type, char const *address, char const *city, char const *state, char const *post_code, char const *country) {
142     gchar *property = g_strdup_printf("%s;%s=%s", ADR, TYPE_PARAM, type);
143     write_multivalue(writer, property, DELIM_COMPONENT, 7, "", "", address, city, state, post_code, country);
144     g_free(property);
145 }
146 
147 void
vcf_write_ORG(vcf_writer * writer,char const * organization,char const * department)148 vcf_write_ORG(vcf_writer *writer, char const *organization, char const *department) {
149     write_multivalue(writer, ORG, DELIM_COMPONENT, 2, organization, department);
150 }
151 
152 void
vcf_write_TEL_internal(vcf_writer * writer,gchar const * number,gint pref,gchar const ** types)153 vcf_write_TEL_internal(vcf_writer *writer, gchar const *number, gint pref, gchar const **types) {
154     gchar *joined_types, *property, *number_uri;
155     g_return_if_fail(pref >= 1 && pref <=100);
156 
157     joined_types = g_strjoinv (";" TYPE_PARAM "=", (gchar **) types);
158     property = g_strdup_printf("%s;%s=%d%s%s", TEL, PREF_PARAM, pref, ";" TYPE_PARAM "=", joined_types);
159     number_uri = g_strdup_printf("%s:%s", TEL_URI_SCHEME, number);
160 
161     write_value(writer, property, number_uri);
162 
163     g_free(number_uri);
164     g_free(property);
165     g_free(joined_types);
166 }
167 
168 void
vcf_write_EMAIL(vcf_writer * writer,gchar const * email,gint pref)169 vcf_write_EMAIL(vcf_writer *writer, gchar const *email, gint pref) {
170     gchar *property;
171     g_return_if_fail(pref >= 1 && pref <=100);
172 
173     property = g_strdup_printf("%s;%s=%d", EMAIL, PREF_PARAM, pref);
174 
175     write_value(writer, property, email);
176 
177     g_free(property);
178 }
179 
180 void
vcf_write_URL(vcf_writer * writer,gchar const * url)181 vcf_write_URL(vcf_writer *writer, gchar const *url) {
182     write_value(writer, URL, url);
183 }
184 
185 void
vcf_write_URL_pref(vcf_writer * writer,gchar const * url,gint pref)186 vcf_write_URL_pref(vcf_writer *writer, gchar const *url, gint pref) {
187     gchar *property;
188     g_return_if_fail(pref >= 1 && pref <= 100);
189 
190     property = g_strdup_printf("%s;%s=%d", URL, PREF_PARAM, pref);
191 
192     write_value(writer, property, url);
193 
194     g_free(property);
195 }
196 void
vcf_write_IMPP(vcf_writer * writer,gchar const * url,gchar const * type)197 vcf_write_IMPP(vcf_writer *writer, gchar const *url, gchar const *type) {
198     gchar *property = g_strdup_printf("%s;%s=%s", IMPP, TYPE_PARAM, type);
199 
200     write_value(writer, property, url);
201 
202     g_free(property);
203 }
204 
205 void
vcf_write_NOTE(vcf_writer * writer,gchar const * note)206 vcf_write_NOTE(vcf_writer *writer, gchar const *note) {
207     write_value(writer, NOTE, note);
208 }
209 
210 /*------------------------------------------------------------------------------*/
211 static void
replace_char(gchar * string,gchar search,gchar const * replacement)212 replace_char(gchar *string, gchar search, gchar const *replacement) {
213     gchar *buffer, *tmp;
214     gsize buffer_size, replacement_size;
215     gint pos = 0;
216 
217     replacement_size = strlen(replacement);
218     buffer_size = strlen(string);
219     if(!buffer_size) {
220         return;
221     }
222     buffer = g_new(gchar, buffer_size*replacement_size+1);
223 
224     tmp = string;
225     while (*tmp) {
226         if (*tmp == search) {
227             strcat(buffer, replacement);
228             pos += replacement_size;
229         } else {
230             buffer[pos] = *tmp;
231             buffer[pos+1] = '\0';
232             pos++;
233         }
234         tmp++;
235     }
236     strcpy(string, buffer);
237     g_free(buffer);
238 }
239 
240 static void
escape_value(gchar * string)241 escape_value(gchar *string) {
242     replace_char(string, '\\', "\\\\");
243     replace_char(string, DELIM_COMPONENT, "\\;");
244     replace_char(string, DELIM_LIST, "\\,");
245     replace_char(string, '\n', "\\n");
246 }
247 
248 static void
write_multivalue(vcf_writer * writer,gchar const * property,gchar separator,gint component_count,...)249 write_multivalue(vcf_writer *writer, gchar const *property, gchar separator, gint component_count, ...) {
250     va_list arguments;
251     gchar **components;
252     gint i, buffer_size;
253     gchar *buffer;
254 
255     components = g_new(gchar *, component_count);
256 
257     buffer_size = strlen(property) + 1; /* property */
258     va_start(arguments, component_count);
259     for (i = 0; i < component_count; i++) {
260         components[i] = va_arg(arguments, gchar *);
261         if (components[i]) {
262             buffer_size += (strlen(components[i]) * MAX_ESCAPE_SIZE); /* escaped value */
263         }
264     }
265     va_end(arguments);
266     buffer_size += (component_count - 1); /* separators */
267     buffer_size++;
268 
269     buffer = g_new(gchar, buffer_size);
270 
271     g_snprintf(buffer, buffer_size, "%s:", property);
272     for (i = 0; i < component_count; i++) {
273         if (i) {
274             gint len = strlen(buffer);
275             buffer[len++] = separator;
276             buffer[len] = '\0';
277         }
278         if (components[i]) {
279             gchar *component_buffer;
280             gint len = strlen(components[i]);
281             component_buffer = g_new(gchar, (len * MAX_ESCAPE_SIZE + 1));
282             strcpy(component_buffer, components[i]);
283             escape_value(component_buffer);
284             strcat(buffer, component_buffer);
285             g_free(component_buffer);
286         }
287     }
288 
289     write_string(writer, buffer);
290 
291     g_free(buffer);
292     g_free(components);
293 }
294 
295 static void
write_value(vcf_writer * writer,gchar const * property_name,gchar const * value)296 write_value(vcf_writer *writer, gchar const *property_name, gchar const * value) {
297     write_multivalue(writer, property_name, '\0', 1, value);
298 }
299 
300 static gchar *
format_date(guint32 julian_day)301 format_date(guint32 julian_day) {
302     gchar *buffer;
303     GDate *cdate;
304     gsize size = 9;
305 
306     buffer = g_new(gchar, size);
307     cdate = g_date_new_julian (julian_day);
308     size = g_date_strftime (buffer, size, "%Y%m%d", cdate);
309     if(!size) {
310         g_free(buffer);
311         return NULL;
312     }
313     return buffer;
314 }
315 
316 static void
write_string(vcf_writer * writer,gchar const * content)317 write_string(vcf_writer *writer, gchar const *content) {
318     gchar *buffer;
319     gsize max_len;
320 
321     buffer = g_new(gchar, MAX_LINE_SIZE + 1);
322     g_return_if_fail(buffer);
323 
324     max_len = MAX_LINE_SIZE;
325     while (*content) {
326         gsize len = strlen(content);
327         if (len > max_len) {
328             gchar *end = g_utf8_find_prev_char(content, content + MAX_LINE_SIZE);
329             len = (end - content);
330             strncpy(buffer, content, len);
331             buffer[len] = '\0';
332             writer->callback(buffer, len, writer->user_data);
333             writer->callback(LINE_FOLD, LINE_FOLD_SIZE, writer->user_data);
334 
335             content = end;
336             max_len = MAX_FOLDED_LINE_SIZE;
337         } else {
338             strcpy(buffer, content);
339             writer->callback(buffer, len, writer->user_data);
340             writer->callback(CRLF, CRLF_SIZE, writer->user_data);
341             content += len;
342         }
343     }
344     g_free(buffer);
345 }