/* * gnome-keyring * * Copyright (C) 2011 Collabora Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * * Author: Stef Walter */ #include "config.h" #include "gcr-record.h" #include #include #include #define MAX_COLUMNS 32 typedef struct { gpointer next; gsize n_value; gchar value[1]; /* Hangs off the end */ } GcrRecordBlock; struct _GcrRecord { GcrRecordBlock *block; const gchar *columns[MAX_COLUMNS]; guint n_columns; gchar delimiter; }; G_DEFINE_BOXED_TYPE (GcrRecord, _gcr_record, _gcr_record_copy, _gcr_record_free); static GcrRecordBlock * record_block_new (const gchar *value, gsize length) { GcrRecordBlock *block; block = g_malloc (sizeof (GcrRecordBlock) + length); block->next = NULL; block->n_value = length; if (value != NULL) { memcpy (block->value, value, length); block->value[length] = 0; } else { block->value[0] = 0; } return block; } static GcrRecordBlock * record_block_take (gchar *value, gsize length) { GcrRecordBlock *block; g_assert (value); block = g_realloc (value, sizeof (GcrRecordBlock) + length); memmove (((gchar*)block) + G_STRUCT_OFFSET (GcrRecordBlock, value), block, length); block->next = NULL; block->n_value = length; block->value[length] = 0; return block; } static GcrRecord * record_flatten (GcrRecord *record) { GcrRecord *result; GcrRecordBlock *block; gsize total; gsize at; gsize len; guint i; /* Calculate the length of what we need */ total = 0; for (i = 0; i < record->n_columns; i++) total += strlen (record->columns[i]) + 1; /* Allocate a new GcrRecordData which will hold all that */ result = g_new0 (GcrRecord, 1); result->block = block = record_block_new (NULL, total); at = 0; for (i = 0; i < record->n_columns; i++) { len = strlen (record->columns[i]); result->columns[i] = block->value + at; memcpy ((gchar *)result->columns[i], record->columns[i], len + 1); at += len + 1; } result->n_columns = record->n_columns; result->delimiter = record->delimiter; g_assert (at == total); return result; } static void print_record_to_string (GcrRecord *record, GString *string) { guint i; for (i = 0; i < record->n_columns; i++) { g_string_append (string, record->columns[i]); g_string_append_c (string, record->delimiter); } } gchar * _gcr_record_format (GcrRecord *record) { GString *string; g_return_val_if_fail (record, NULL); string = g_string_new (""); print_record_to_string (record, string); return g_string_free (string, FALSE); } GcrRecord * _gcr_record_new (GQuark schema, guint n_columns, gchar delimiter) { GcrRecord *result; guint i; result = g_new0 (GcrRecord, 1); result->block = NULL; result->delimiter = delimiter; for (i = 0; i < n_columns; i++) result->columns[i] = ""; result->columns[0] = g_quark_to_string (schema); result->n_columns = n_columns; return result; } GcrRecord * _gcr_record_copy (GcrRecord *record) { return record_flatten (record); } static GcrRecord * take_and_parse_internal (GcrRecordBlock *block, gchar delimiter, gboolean allow_empty) { GcrRecord *result; gchar *at, *beg, *end; g_assert (block); result = g_new0 (GcrRecord, 1); result->block = block; result->delimiter = delimiter; g_debug ("parsing line %s", block->value); at = block->value; for (;;) { if (result->n_columns >= MAX_COLUMNS) { g_debug ("too many record (%d) in gnupg line", MAX_COLUMNS); _gcr_record_free (result); return NULL; } beg = at; result->columns[result->n_columns] = beg; at = strchr (beg, delimiter); if (at == NULL) { end = (block->value + block->n_value) - 1; } else { at[0] = '\0'; end = at; at++; } if (allow_empty || end > beg) result->n_columns++; if (at == NULL) break; } return result; } GcrRecord* _gcr_record_parse_colons (const gchar *line, gssize n_line) { g_return_val_if_fail (line, NULL); if (n_line < 0) n_line = strlen (line); return take_and_parse_internal (record_block_new (line, n_line), ':', TRUE); } GcrRecord* _gcr_record_parse_spaces (const gchar *line, gssize n_line) { g_return_val_if_fail (line, NULL); if (n_line < 0) n_line = strlen (line); return take_and_parse_internal (record_block_new (line, n_line), ' ', FALSE); } guint _gcr_record_get_count (GcrRecord *record) { g_return_val_if_fail (record, 0); return record->n_columns; } static void record_take_column (GcrRecord *record, guint column, GcrRecordBlock *block) { g_assert (block->next == NULL); block->next = record->block; record->block = block; g_assert (column < record->n_columns); record->columns[column] = block->value; } static const char HEXC_LOWER[] = "0123456789abcdef"; /* Will return NULL if unescaping failed or not needed */ static gchar * c_colons_unescape (const gchar *source, gsize *length) { const gchar *p = source, *octal, *hex; gchar *dest = NULL; gchar *q = dest; gchar *pos; while (*p) { if (*p == '\\') { if (dest == NULL) { dest = g_malloc (strlen (source) + 1); memcpy (dest, source, (p - source)); q = dest + (p - source); } p++; switch (*p) { case '\0': /* invalid trailing backslash */ g_free (dest); return NULL; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': *q = 0; octal = p; while ((p < octal + 3) && (*p >= '0') && (*p <= '7')) { *q = (*q * 8) + (*p - '0'); p++; } q++; p--; break; case 'x': *q = 0; hex = p; while (p < hex + 2) { pos = strchr (HEXC_LOWER, g_ascii_tolower (*p)); if (pos == 0) { /* invalid bad hex character */ g_free (dest); return NULL; } *q = (*q * 16) + (pos - HEXC_LOWER); p++; } q++; p--; break; case 'b': *q++ = '\b'; break; case 'f': *q++ = '\f'; break; case 'n': *q++ = '\n'; break; case 'r': *q++ = '\r'; break; case 't': *q++ = '\t'; break; default: /* Also handles \" and \\ */ *q++ = *p; break; } } else if (q != NULL) { *q++ = *p; } p++; } if (q != NULL) { *q = 0; if (length) *length = q - dest; } return dest; } /* Will return NULL if no escaping needed */ static gchar * c_colons_escape (const gchar *source, const gchar extra, gsize *length) { const guchar *p; gchar *dest = NULL; gchar *q = NULL; gchar escape; gsize off; g_return_val_if_fail (source != NULL, NULL); p = (guchar *) source; while (*p) { escape = 0; switch (*p) { case '\b': escape = 'b'; break; case '\f': escape = 'f'; break; case '\n': escape = 'n'; break; case '\r': escape = 'r'; break; case '\t': escape = 't'; break; case '\\': escape = '\\'; break; case '"': escape = '"'; break; } if (escape != 0 || *p < ' ' || *p >= 0x127 || *p == extra) { if (dest == NULL) { /* Each source byte needs maximally four destination chars (\xff) */ dest = g_malloc (strlen (source) * 4 + 1); off = (gchar *)p - source; memcpy (dest, source, off); q = dest + off; } if (escape) { *q++ = '\\'; *q++ = escape; } else { *q++ = '\\'; *q++ = 'x'; *q++ = HEXC_LOWER[*p >> 4 & 0xf]; *q++ = HEXC_LOWER[*p & 0xf]; } } else if (q != NULL) { *q++ = *p; } p++; } if (q != NULL) { *q = 0; if (length) *length = q - dest; } return dest; } gchar* _gcr_record_get_string (GcrRecord *record, guint column) { const gchar *value; gchar *text = NULL; g_return_val_if_fail (record, NULL); value = _gcr_record_get_raw (record, column); if (!value) return NULL; text = c_colons_unescape (value, NULL); if (text != NULL) value = text; /* If it's not UTF-8, we guess that it's latin1 */ if (!g_utf8_validate (value, -1, NULL)) { gchar *conv = g_convert (value, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); g_free (text); value = text = conv; } /* * latin1 to utf-8 conversion can't really fail, just produce * garbage... so there's no need to check here. */ return (text == value) ? text : g_strdup (value); } void _gcr_record_set_string (GcrRecord *record, guint column, const gchar *string) { GcrRecordBlock *block; gchar *escaped; g_return_if_fail (record != NULL); g_return_if_fail (string != NULL); g_return_if_fail (column < record->n_columns); escaped = c_colons_escape (string, record->delimiter, NULL); if (escaped != NULL) block = record_block_take (escaped, strlen (escaped)); else block = record_block_new (string, strlen (string)); record_take_column (record, column, block); } gchar _gcr_record_get_char (GcrRecord *record, guint column) { const gchar *value; g_return_val_if_fail (record, 0); value = _gcr_record_get_raw (record, column); if (!value) return 0; if (value[0] != 0 && value[1] == 0) return value[0]; return 0; } void _gcr_record_set_char (GcrRecord *record, guint column, gchar value) { g_return_if_fail (record != NULL); g_return_if_fail (column < record->n_columns); g_return_if_fail (value != 0); record_take_column (record, column, record_block_new (&value, 1)); } gboolean _gcr_record_get_uint (GcrRecord *record, guint column, guint *value) { const gchar *raw; gint64 result; gchar *end = NULL; g_return_val_if_fail (record, FALSE); raw = _gcr_record_get_raw (record, column); if (raw == NULL) return FALSE; result = g_ascii_strtoll (raw, &end, 10); if (!end || end[0]) { g_debug ("invalid unsigned integer value: %s", raw); return FALSE; } if (result < 0 || result > G_MAXUINT32) { g_debug ("unsigned integer value is out of range: %s", raw); return FALSE; } if (value) *value = (guint)result; return TRUE; } void _gcr_record_set_uint (GcrRecord *record, guint column, guint value) { gchar *escaped; g_return_if_fail (record != NULL); g_return_if_fail (column < record->n_columns); escaped = g_strdup_printf ("%u", value); record_take_column (record, column, record_block_take (escaped, strlen (escaped))); } gboolean _gcr_record_get_ulong (GcrRecord *record, guint column, gulong *value) { const gchar *raw; gint64 result; gchar *end = NULL; g_return_val_if_fail (record, FALSE); raw = _gcr_record_get_raw (record, column); if (raw == NULL) return FALSE; result = g_ascii_strtoull (raw, &end, 10); if (!end || end[0]) { g_debug ("invalid unsigned long value: %s", raw); return FALSE; } if (result < 0 || result > G_MAXULONG) { g_debug ("unsigned long value is out of range: %s", raw); return FALSE; } if (value) *value = (guint)result; return TRUE; } void _gcr_record_set_ulong (GcrRecord *record, guint column, gulong value) { gchar *escaped; g_return_if_fail (record != NULL); g_return_if_fail (column < record->n_columns); escaped = g_strdup_printf ("%lu", value); record_take_column (record, column, record_block_take (escaped, strlen (escaped))); } GDateTime * _gcr_record_get_date (GcrRecord *record, guint column) { const char *raw; guint64 result; char *end = NULL; GTimeZone *tz; GDateTime *ret; g_return_val_if_fail (record, NULL); raw = _gcr_record_get_raw (record, column); if (raw == NULL) return NULL; /* Try to parse as a number */ result = g_ascii_strtoull (raw, &end, 10); if (end != NULL && end[0] == '\0') { if (result == 0) return NULL; else return g_date_time_new_from_unix_utc (result); } /* Try to parse as a date */ tz = g_time_zone_new_utc (); ret = g_date_time_new_from_iso8601 (raw, tz); g_time_zone_unref (tz); return ret; } /** * _gcr_record_get_base64: * @record: The record * @column: The column to decode. * @n_data: Location to return size of returned data. * * Decode a column of a record as base64 data. * * Returns: (transfer full): The decoded value, or %NULL if not found. */ gpointer _gcr_record_get_base64 (GcrRecord *record, guint column, gsize *n_data) { const gchar *raw; g_return_val_if_fail (record, NULL); raw = _gcr_record_get_raw (record, column); if (raw == NULL) return NULL; return g_base64_decode (raw, n_data); } void _gcr_record_set_base64 (GcrRecord *record, guint column, gconstpointer data, gsize n_data) { GcrRecordBlock *block; gint state, save; gsize estimate; gsize length; g_return_if_fail (record != NULL); g_return_if_fail (column < record->n_columns); estimate = n_data * 4 / 3 + n_data * 4 / (3 * 65) + 7; block = record_block_new (NULL, estimate); /* The actual base64 data, without line breaks */ state = save = 0; length = g_base64_encode_step ((guchar *)data, n_data, FALSE, block->value, &state, &save); length += g_base64_encode_close (TRUE, block->value + length, &state, &save); block->value[length] = 0; g_assert (length < estimate); g_strchomp (block->value); record_take_column (record, column, block); } const gchar* _gcr_record_get_raw (GcrRecord *record, guint column) { g_return_val_if_fail (record, NULL); if (column >= record->n_columns) { g_debug ("only %d columns exist, tried to access %d", record->n_columns, column); return NULL; } return record->columns[column]; } void _gcr_record_set_raw (GcrRecord *record, guint column, const gchar *value) { g_return_if_fail (record != NULL); g_return_if_fail (value != NULL); g_return_if_fail (column < record->n_columns); record_take_column (record, column, record_block_new (value, strlen (value))); } void _gcr_record_take_raw (GcrRecord *record, guint column, gchar *value) { g_return_if_fail (record != NULL); g_return_if_fail (value != NULL); g_return_if_fail (column < record->n_columns); record_take_column (record, column, record_block_take (value, strlen (value))); } void _gcr_record_free (gpointer record) { GcrRecordBlock *block, *next; GcrRecord *rec = record; if (!record) return; for (block = rec->block; block != NULL; block = next) { next = block->next; g_free (block); } g_free (record); } GQuark _gcr_record_get_schema (GcrRecord *record) { const gchar *value; value = _gcr_record_get_raw (record, GCR_RECORD_SCHEMA); if (value != NULL) return g_quark_try_string (value); return 0; } GcrRecord * _gcr_records_find (GPtrArray *records, GQuark schema) { guint i; g_return_val_if_fail (records, NULL); g_return_val_if_fail (schema, NULL); for (i = 0; i < records->len; i++) { if (schema == _gcr_record_get_schema (records->pdata[i])) return records->pdata[i]; } return NULL; } gchar * _gcr_records_format (GPtrArray *records) { GString *string; guint i; g_return_val_if_fail (records, NULL); string = g_string_new (""); for (i = 0; i < records->len; i++) { print_record_to_string (records->pdata[i], string); g_string_append_c (string, '\n'); } return g_string_free (string, FALSE); } static gchar ** strnsplit (const gchar *string, gsize length, gchar delimiter) { GSList *string_list = NULL, *slist; gchar **str_array, *s; guint n = 0; const gchar *remainder; const gchar *end; g_return_val_if_fail (string != NULL, NULL); g_return_val_if_fail (delimiter != '\0', NULL); end = string + length; remainder = string; s = memchr (remainder, delimiter, end - remainder); if (s) { while (s) { gsize len; len = s - remainder; string_list = g_slist_prepend (string_list, g_strndup (remainder, len)); n++; remainder = s + 1; s = memchr (remainder, delimiter, end - remainder); } } if (*string) { n++; string_list = g_slist_prepend (string_list, g_strndup (remainder, end - remainder)); } str_array = g_new (gchar*, n + 1); str_array[n--] = NULL; for (slist = string_list; slist; slist = slist->next) str_array[n--] = slist->data; g_slist_free (string_list); return str_array; } GPtrArray * _gcr_records_parse_colons (gconstpointer data, gssize n_data) { GPtrArray *result = NULL; GcrRecordBlock *block; GcrRecord *record; gchar **lines; guint i; lines = strnsplit (data, n_data, '\n'); result = g_ptr_array_new_with_free_func (_gcr_record_free); for (i = 0; lines[i] != NULL; i++) { block = record_block_take (lines[i], strlen (lines[i])); record = take_and_parse_internal (block, ':', TRUE); if (record == NULL) { g_ptr_array_unref (result); result = NULL; break; } g_ptr_array_add (result, record); } /* Free any not done */ for (; lines[i] != NULL; i++) g_free (lines[i]); /* Individual lines already freed */ g_free (lines); return result; }