1 /*
2  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
3  * Copyright (C) 2011 Vivien Malerba <malerba@gnome-db.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <glib/gi18n-lib.h>
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include "entry-properties.h"
24 #include "marshal.h"
25 #include <time.h>
26 #include <libgda-ui/libgda-ui.h>
27 #include "../text-search.h"
28 
29 struct _EntryPropertiesPrivate {
30 	BrowserConnection *bcnc;
31 
32 	GtkTextView *view;
33 	GtkTextBuffer *text;
34 	gboolean hovering_over_link;
35 
36 	GtkWidget *text_search;
37 
38 	/* coordinates of mouse */
39 	gint bx;
40 	gint by;
41 };
42 
43 static void entry_properties_class_init (EntryPropertiesClass *klass);
44 static void entry_properties_init       (EntryProperties *eprop, EntryPropertiesClass *klass);
45 static void entry_properties_dispose   (GObject *object);
46 
47 static GObjectClass *parent_class = NULL;
48 
49 /* signals */
50 enum {
51         OPEN_DN,
52 	OPEN_CLASS,
53         LAST_SIGNAL
54 };
55 
56 gint entry_properties_signals [LAST_SIGNAL] = { 0, 0 };
57 
58 /*
59  * EntryProperties class implementation
60  */
61 
62 static void
entry_properties_class_init(EntryPropertiesClass * klass)63 entry_properties_class_init (EntryPropertiesClass *klass)
64 {
65 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
66 	parent_class = g_type_class_peek_parent (klass);
67 
68 	entry_properties_signals [OPEN_DN] =
69 		g_signal_new ("open-dn",
70                               G_TYPE_FROM_CLASS (object_class),
71                               G_SIGNAL_RUN_FIRST,
72                               G_STRUCT_OFFSET (EntryPropertiesClass, open_dn),
73                               NULL, NULL,
74                               _ldap_marshal_VOID__STRING, G_TYPE_NONE,
75                               1, G_TYPE_STRING);
76 	entry_properties_signals [OPEN_CLASS] =
77 		g_signal_new ("open-class",
78                               G_TYPE_FROM_CLASS (object_class),
79                               G_SIGNAL_RUN_FIRST,
80                               G_STRUCT_OFFSET (EntryPropertiesClass, open_class),
81                               NULL, NULL,
82                               _ldap_marshal_VOID__STRING, G_TYPE_NONE,
83                               1, G_TYPE_STRING);
84 	klass->open_dn = NULL;
85 	klass->open_class = NULL;
86 
87 	object_class->dispose = entry_properties_dispose;
88 }
89 
90 
91 static void
entry_properties_init(EntryProperties * eprop,G_GNUC_UNUSED EntryPropertiesClass * klass)92 entry_properties_init (EntryProperties *eprop, G_GNUC_UNUSED EntryPropertiesClass *klass)
93 {
94 	eprop->priv = g_new0 (EntryPropertiesPrivate, 1);
95 	eprop->priv->hovering_over_link = FALSE;
96 
97 	gtk_orientable_set_orientation (GTK_ORIENTABLE (eprop), GTK_ORIENTATION_VERTICAL);
98 }
99 
100 static void
entry_properties_dispose(GObject * object)101 entry_properties_dispose (GObject *object)
102 {
103 	EntryProperties *eprop = (EntryProperties *) object;
104 
105 	/* free memory */
106 	if (eprop->priv) {
107 		if (eprop->priv->bcnc) {
108 			g_object_unref (eprop->priv->bcnc);
109 		}
110 
111 		g_free (eprop->priv);
112 		eprop->priv = NULL;
113 	}
114 
115 	parent_class->dispose (object);
116 }
117 
118 GType
entry_properties_get_type(void)119 entry_properties_get_type (void)
120 {
121 	static GType type = 0;
122 
123 	if (G_UNLIKELY (type == 0)) {
124 		static const GTypeInfo columns = {
125 			sizeof (EntryPropertiesClass),
126 			(GBaseInitFunc) NULL,
127 			(GBaseFinalizeFunc) NULL,
128 			(GClassInitFunc) entry_properties_class_init,
129 			NULL,
130 			NULL,
131 			sizeof (EntryProperties),
132 			0,
133 			(GInstanceInitFunc) entry_properties_init,
134 			0
135 		};
136 		type = g_type_register_static (GTK_TYPE_BOX, "EntryProperties", &columns, 0);
137 	}
138 	return type;
139 }
140 
141 static gboolean key_press_event (GtkWidget *text_view, GdkEventKey *event, EntryProperties *eprop);
142 static gboolean event_after (GtkWidget *text_view, GdkEvent *ev, EntryProperties *eprop);
143 static gboolean motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, EntryProperties *eprop);
144 static gboolean visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event, EntryProperties *eprop);
145 static void populate_popup_cb (GtkWidget *text_view, GtkMenu *menu, EntryProperties *eprop);
146 
147 static void show_search_bar (EntryProperties *eprop);
148 
149 /**
150  * entry_properties_new:
151  *
152  * Returns: a new #GtkWidget
153  */
154 GtkWidget *
entry_properties_new(BrowserConnection * bcnc)155 entry_properties_new (BrowserConnection *bcnc)
156 {
157 	EntryProperties *eprop;
158 	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
159 
160 	eprop = ENTRY_PROPERTIES (g_object_new (ENTRY_PROPERTIES_TYPE, NULL));
161 	eprop->priv->bcnc = g_object_ref (bcnc);
162 
163 	GtkWidget *sw;
164         sw = gtk_scrolled_window_new (NULL, NULL);
165         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
166         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
167                                         GTK_POLICY_AUTOMATIC,
168                                         GTK_POLICY_AUTOMATIC);
169 	gtk_box_pack_start (GTK_BOX (eprop), sw, TRUE, TRUE, 0);
170 
171 	GtkWidget *textview;
172 	textview = gtk_text_view_new ();
173         gtk_container_add (GTK_CONTAINER (sw), textview);
174         gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
175         gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
176         gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
177 	gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (textview), FALSE);
178         eprop->priv->text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
179 	eprop->priv->view = GTK_TEXT_VIEW (textview);
180         gtk_widget_show_all (sw);
181 
182         gtk_text_buffer_create_tag (eprop->priv->text, "section",
183                                     "weight", PANGO_WEIGHT_BOLD,
184                                     "foreground", "blue", NULL);
185 
186         gtk_text_buffer_create_tag (eprop->priv->text, "error",
187                                     "foreground", "red", NULL);
188 
189         gtk_text_buffer_create_tag (eprop->priv->text, "data",
190 				    "left-margin", 20, NULL);
191 
192         gtk_text_buffer_create_tag (eprop->priv->text, "convdata",
193 				    "style", PANGO_STYLE_ITALIC,
194 				    "background", "lightgray",
195                                     "left-margin", 20, NULL);
196 
197         gtk_text_buffer_create_tag (eprop->priv->text, "starter",
198                                     "indent", -10,
199                                     "left-margin", 20, NULL);
200 
201 	g_signal_connect (textview, "key-press-event",
202 			  G_CALLBACK (key_press_event), eprop);
203 	g_signal_connect (textview, "event-after",
204 			  G_CALLBACK (event_after), eprop);
205 	g_signal_connect (textview, "motion-notify-event",
206 			  G_CALLBACK (motion_notify_event), eprop);
207 	g_signal_connect (textview, "visibility-notify-event",
208 			  G_CALLBACK (visibility_notify_event), eprop);
209 	g_signal_connect (textview, "populate-popup",
210 			  G_CALLBACK (populate_popup_cb), eprop);
211 
212 	entry_properties_set_dn (eprop, NULL);
213 
214 	return (GtkWidget*) eprop;
215 }
216 
217 static void
data_save_cb(GtkWidget * mitem,EntryProperties * eprop)218 data_save_cb (GtkWidget *mitem, EntryProperties *eprop)
219 {
220 	GtkWidget *dialog;
221 
222 	dialog = gtk_file_chooser_dialog_new (_("Select the file to save data to"),
223 					      (GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (eprop)),
224 					      GTK_FILE_CHOOSER_ACTION_SAVE,
225 					      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
226 					      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
227 					      NULL);
228 	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
229 					     gdaui_get_default_path ());
230 	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
231 		char *filename;
232 		GValue *binvalue;
233 		GError *lerror = NULL;
234 		const GdaBinary *bin = NULL;
235 
236 		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
237 		binvalue = g_object_get_data (G_OBJECT (mitem), "binvalue");
238 		if (binvalue)
239 			bin = gda_value_get_binary (binvalue);
240 		if (!bin || !g_file_set_contents (filename, (gchar*) bin->data,
241 						  bin->binary_length, &lerror)) {
242 			browser_show_error ((GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (eprop)),
243 					    _("Could not save data: %s"),
244 					    lerror && lerror->message ? lerror->message : _("No detail"));
245 			g_clear_error (&lerror);
246 		}
247 		gdaui_set_default_path (gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)));
248 		g_free (filename);
249 	}
250 	gtk_widget_destroy (dialog);
251 }
252 
253 static void
populate_popup_cb(G_GNUC_UNUSED GtkWidget * text_view,GtkMenu * menu,EntryProperties * eprop)254 populate_popup_cb (G_GNUC_UNUSED GtkWidget *text_view, GtkMenu *menu, EntryProperties *eprop)
255 {
256 	GtkTextIter iter;
257 	gtk_text_view_get_iter_at_position (eprop->priv->view, &iter, NULL,
258 					    eprop->priv->bx, eprop->priv->by);
259 
260 	GSList *tags = NULL, *tagp = NULL;
261 
262 	tags = gtk_text_iter_get_tags (&iter);
263 	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
264 		GtkTextTag *tag = tagp->data;
265 		GValue *binvalue;
266 
267 		binvalue = g_object_get_data (G_OBJECT (tag), "binvalue");
268 		if (binvalue) {
269 			GtkWidget *item;
270 
271 			item = gtk_separator_menu_item_new ();
272 			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
273 			gtk_widget_show (item);
274 
275 			item = gtk_menu_item_new_with_label (_("Save"));
276 			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
277 			g_signal_connect (G_OBJECT (item), "activate",
278 					  G_CALLBACK (data_save_cb), eprop);
279 			g_object_set_data (G_OBJECT (item), "binvalue", binvalue);
280 			gtk_widget_show (item);
281 
282 			break;
283 		}
284         }
285 
286 	if (tags)
287 		g_slist_free (tags);
288 }
289 
290 static GdkCursor *hand_cursor = NULL;
291 static GdkCursor *regular_cursor = NULL;
292 
293 /* Looks at all tags covering the position (x, y) in the text view,
294  * and if one of them is a link, change the cursor to the "hands" cursor
295  * typically used by web browsers.
296  */
297 static void
set_cursor_if_appropriate(GtkTextView * text_view,gint x,gint y,EntryProperties * eprop)298 set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, EntryProperties *eprop)
299 {
300 	GSList *tags = NULL, *tagp = NULL;
301 	GtkTextIter iter;
302 	gboolean hovering = FALSE;
303 
304 	gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
305 
306 	tags = gtk_text_iter_get_tags (&iter);
307 	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
308 		GtkTextTag *tag = tagp->data;
309 
310 		if (g_object_get_data (G_OBJECT (tag), "dn") ||
311 		    g_object_get_data (G_OBJECT (tag), "class")) {
312 			hovering = TRUE;
313 			break;
314 		}
315 	}
316 
317 	if (hovering != eprop->priv->hovering_over_link) {
318 		eprop->priv->hovering_over_link = hovering;
319 
320 		if (eprop->priv->hovering_over_link) {
321 			if (! hand_cursor)
322 				hand_cursor = gdk_cursor_new (GDK_HAND2);
323 			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
324 									 GTK_TEXT_WINDOW_TEXT),
325 					       hand_cursor);
326 		}
327 		else {
328 			if (!regular_cursor)
329 				regular_cursor = gdk_cursor_new (GDK_XTERM);
330 			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
331 									 GTK_TEXT_WINDOW_TEXT),
332 					       regular_cursor);
333 		}
334 	}
335 
336 	if (tags)
337 		g_slist_free (tags);
338 }
339 
340 /*
341  * Also update the cursor image if the window becomes visible
342  * (e.g. when a window covering it got iconified).
343  */
344 static gboolean
visibility_notify_event(GtkWidget * text_view,G_GNUC_UNUSED GdkEventVisibility * event,EntryProperties * eprop)345 visibility_notify_event (GtkWidget *text_view, G_GNUC_UNUSED GdkEventVisibility *event,
346 			 EntryProperties *eprop)
347 {
348 	gint wx, wy, bx, by;
349 	GdkDeviceManager *manager;
350         GdkDevice *pointer;
351 
352 	manager = gdk_display_get_device_manager (gtk_widget_get_display (text_view));
353 	pointer = gdk_device_manager_get_client_pointer (manager);
354 	gdk_window_get_device_position (gtk_widget_get_window (text_view), pointer, &wx, &wy, NULL);
355 	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
356 					       GTK_TEXT_WINDOW_WIDGET,
357 					       wx, wy, &bx, &by);
358 
359 	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by, eprop);
360 
361 	return FALSE;
362 }
363 
364 /*
365  * Update the cursor image if the pointer moved.
366  */
367 static gboolean
motion_notify_event(GtkWidget * text_view,GdkEventMotion * event,EntryProperties * eprop)368 motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, EntryProperties *eprop)
369 {
370 	gint x, y;
371 
372 	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
373 					       GTK_TEXT_WINDOW_WIDGET,
374 					       event->x, event->y, &x, &y);
375 
376 	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y, eprop);
377 
378 	/* store coordinates */
379 	eprop->priv->bx = x;
380 	eprop->priv->by = y;
381 
382 	return FALSE;
383 }
384 
385 /* Looks at all tags covering the position of iter in the text view,
386  * and if one of them is a link, follow it by showing the page identified
387  * by the data attached to it.
388  */
389 static void
follow_if_link(G_GNUC_UNUSED GtkWidget * text_view,GtkTextIter * iter,EntryProperties * eprop)390 follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, EntryProperties *eprop)
391 {
392 	GSList *tags = NULL, *tagp = NULL;
393 
394 	tags = gtk_text_iter_get_tags (iter);
395 	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
396 		GtkTextTag *tag = tagp->data;
397 		const gchar *dn;
398 
399 		dn = g_object_get_data (G_OBJECT (tag), "dn");
400 		if (dn)
401 			g_signal_emit (eprop, entry_properties_signals [OPEN_DN], 0, dn);
402 		dn = g_object_get_data (G_OBJECT (tag), "class");
403 		if (dn)
404 			g_signal_emit (eprop, entry_properties_signals [OPEN_CLASS], 0, dn);
405         }
406 
407 	if (tags)
408 		g_slist_free (tags);
409 }
410 
411 
412 /*
413  * Links can also be activated by clicking.
414  */
415 static gboolean
event_after(GtkWidget * text_view,GdkEvent * ev,EntryProperties * eprop)416 event_after (GtkWidget *text_view, GdkEvent *ev, EntryProperties *eprop)
417 {
418 	GtkTextIter start, end, iter;
419 	GtkTextBuffer *buffer;
420 	GdkEventButton *event;
421 	gint x, y;
422 
423 	if (ev->type != GDK_BUTTON_RELEASE)
424 		return FALSE;
425 
426 	event = (GdkEventButton *)ev;
427 
428 	if (event->button != 1)
429 		return FALSE;
430 
431 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
432 
433 	/* we shouldn't follow a link if the user has selected something */
434 	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
435 	if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
436 		return FALSE;
437 
438 	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
439 					       GTK_TEXT_WINDOW_WIDGET,
440 					       event->x, event->y, &x, &y);
441 
442 	gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
443 
444 	follow_if_link (text_view, &iter, eprop);
445 
446 	return FALSE;
447 }
448 
449 /*
450  * Links can be activated by pressing Enter.
451  */
452 static gboolean
key_press_event(GtkWidget * text_view,GdkEventKey * event,EntryProperties * eprop)453 key_press_event (GtkWidget *text_view, GdkEventKey *event, EntryProperties *eprop)
454 {
455 	GtkTextIter iter;
456 	GtkTextBuffer *buffer;
457 
458 	switch (event->keyval) {
459 	case GDK_KEY_Return:
460 	case GDK_KEY_KP_Enter:
461 		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
462 		gtk_text_buffer_get_iter_at_mark (buffer, &iter,
463 						  gtk_text_buffer_get_insert (buffer));
464 		follow_if_link (text_view, &iter, eprop);
465 		break;
466 	case GDK_KEY_F:
467 	case GDK_KEY_f:
468 		if (event->state & GDK_CONTROL_MASK) {
469 			show_search_bar (eprop);
470 			return TRUE;
471 		}
472 		break;
473 	case GDK_KEY_slash:
474 		show_search_bar (eprop);
475 		return TRUE;
476 		break;
477 	default:
478 		break;
479 	}
480 	return FALSE;
481 }
482 
483 static GdkPixbuf *
data_to_pixbuf(const GValue * cvalue)484 data_to_pixbuf (const GValue *cvalue)
485 {
486 	GdkPixbuf *retpixbuf = NULL;
487 
488 	if (G_VALUE_TYPE (cvalue) == GDA_TYPE_BINARY) {
489 		const GdaBinary *bin;
490 		GdkPixbufLoader *loader;
491 
492 		bin = gda_value_get_binary (cvalue);
493 		if (!bin->data)
494 			goto out;
495 
496 		loader = gdk_pixbuf_loader_new ();
497 		if (gdk_pixbuf_loader_write (loader, bin->data, bin->binary_length, NULL)) {
498 			if (gdk_pixbuf_loader_close (loader, NULL)) {
499 				retpixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
500 				g_object_ref (retpixbuf);
501 			}
502 			else
503 				gdk_pixbuf_loader_close (loader, NULL);
504 		}
505 		else
506 			gdk_pixbuf_loader_close (loader, NULL);
507 		g_object_unref (loader);
508 	}
509 
510  out:
511 	return retpixbuf;
512 }
513 
514 static gchar *
unix_shadow_to_string(const gchar * value,const gchar * attname)515 unix_shadow_to_string (const gchar *value, const gchar *attname)
516 {
517 	/* value is the number of days since 1970-01-01 */
518 	gint64 i64;
519 	gchar *endptr [1];
520 
521 	if (!value || !*value)
522 		return NULL;
523 
524 	i64 = g_ascii_strtoll (value, endptr, 10);
525 	if (**endptr != '\0')
526 		return NULL;
527 
528 	if ((i64 == -1) &&
529 	    (!strcmp (attname, "shadowInactive") ||
530 	     !strcmp (attname, "shadowMin") ||
531 	     !strcmp (attname, "shadowExpire")))
532 		return g_strdup (_("Non activated"));
533 	else if ((i64 == 99999) && !strcmp (attname, "shadowMax"))
534 		return g_strdup ("Always valid");
535 	if ((i64 >= G_MAXUINT) || (i64 < 0))
536 		return NULL;
537 
538 	if (!strcmp (attname, "shadowMax") ||
539 	    !strcmp (attname, "shadowMin") ||
540 	    !strcmp (attname, "shadowInactive"))
541 		return NULL;
542 
543 	GDate *date;
544 	date = g_date_new_dmy (1, 1, 1970);
545 	g_date_add_days (date, (guint) i64);
546 	if (! g_date_valid (date)) {
547 		g_date_free (date);
548 		return NULL;
549 	}
550 
551 	GdaDataHandler *dh;
552 	GValue tvalue;
553 	gchar *str;
554 
555 	memset (&tvalue, 0, sizeof (GValue));
556 	g_value_init (&tvalue, G_TYPE_DATE);
557 	g_value_take_boxed (&tvalue, date);
558 	dh = gda_data_handler_get_default (G_TYPE_DATE);
559 	str = gda_data_handler_get_str_from_value (dh, &tvalue);
560 	g_value_reset (&tvalue);
561 
562 	return str;
563 }
564 
565 static gchar *
ad_1601_timestamp_to_string(const gchar * value,const gchar * attname)566 ad_1601_timestamp_to_string (const gchar *value, const gchar *attname)
567 {
568 	/* value is the number of 100 nanoseconds since 1601-01-01 UTC */
569 	gint64 i64;
570 	gchar *endptr [1];
571 
572 	if (!value || !*value)
573 		return NULL;
574 
575 	i64 = g_ascii_strtoll (value, endptr, 10);
576 	if (**endptr != '\0')
577 		return NULL;
578 
579 	if (i64 == 0x7FFFFFFFFFFFFFFF)
580 		return g_strdup (_("Never"));
581 
582 	if (i64 == 0 && attname) {
583 		if (!strcmp (attname, "accountExpires"))
584 			return g_strdup (_("Never"));
585 		else
586 			return g_strdup (_("Unknown"));
587 	}
588 
589 	i64 = (i64 / (guint64) 10000000);
590 	if (i64 < (gint64) 11644473600)
591 		return NULL;
592 	i64 = i64 - (guint64) 11644473600;
593 	if (i64 >= G_MAXINT)
594 		return NULL;
595 
596 	GdaDataHandler *dh;
597 	struct tm *stm;
598 	GValue tvalue;
599 	GdaTimestamp ts;
600 	time_t nsec = (time_t) i64;
601 	gchar *str;
602 #ifdef HAVE_LOCALTIME_R
603 	struct tm tmpstm;
604 	stm = localtime_r (&nsec, &tmpstm);
605 #elif HAVE_LOCALTIME_S
606 	struct tm tmpstm;
607 	if (localtime_s (&tmpstm, &nsec) == 0)
608 		stm = &tmpstm;
609 	else
610 		stm = NULL;
611 #else
612 	stm = localtime (&nsec);
613 #endif
614 
615 	if (!stm)
616 		return NULL;
617 
618 	memset (&ts, 0, sizeof (GdaTimestamp));
619 	ts.year = stm->tm_year + 1900;
620 	ts.month = stm->tm_mon + 1;
621 	ts.day = stm->tm_mday;
622 	ts.hour = stm->tm_hour;
623 	ts.minute = stm->tm_min;
624 	ts.second = stm->tm_sec;
625 	ts.timezone = GDA_TIMEZONE_INVALID;
626 	memset (&tvalue, 0, sizeof (GValue));
627 	gda_value_set_timestamp (&tvalue, &ts);
628 	dh = gda_data_handler_get_default (GDA_TYPE_TIMESTAMP);
629 	str = gda_data_handler_get_str_from_value (dh, &tvalue);
630 	g_value_reset (&tvalue);
631 
632 	return str;
633 }
634 
635 typedef struct {
636 	guint mask;
637 	gchar *descr;
638 } ADUACData;
639 
640 ADUACData uac_data[] = {
641 	{0x00000001, "Logon script is executed"},
642 	{0x00000002, "Account disabled"},
643 	{0x00000008, "Home directory required"},
644 	{0x00000010, "Account locked out"},
645 	{0x00000020, "No password required"},
646 	{0x00000040, "User cannot change password"},
647 	{0x00000080, "User can send an encrypted password"},
648 	{0x00000100, "Duplicate account (local user account)"},
649 	{0x00000200, "Default account type"},
650 	{0x00000800, "Permit to trust account for a system domain that trusts other domains"},
651 	{0x00001000, "Account for a computer"},
652 	{0x00002000, "Account for a system backup domain controller"},
653 	{0x00010000, "Account never expires"},
654 	{0x00020000, "Majority Node Set (MNS) logon account"},
655 	{0x00040000, "User must log on using a smart card"},
656 	{0x00080000, "Service account for trusted for Kerberos delegation"},
657 	{0x00100000, "Security context not delegated"},
658 	{0x00200000, "Only Data Encryption Standard (DES) encryption for keys"},
659 	{0x00400000, "Kerberos pre-authentication not required"},
660 	{0x00800000, "User password expired"},
661 	{0x01000000, "Account enabled for delegation"}
662 };
663 
664 static gchar *
ad_1601_uac_to_string(const gchar * value)665 ad_1601_uac_to_string (const gchar *value)
666 {
667 	gint64 i64;
668 	gchar *endptr [1];
669 
670 	if (!value || !*value)
671 		return NULL;
672 
673 	i64 = g_ascii_strtoll (value, endptr, 10);
674 	if (**endptr != '\0')
675 		return NULL;
676 	if (i64 < 0)
677 		return NULL;
678 	if (i64 > G_MAXUINT32)
679 		return NULL;
680 
681 	GString *string = NULL;
682 	guint i;
683 	guint32 v;
684 	v = (guint32) i64;
685 	for (i = 0; i < sizeof (uac_data) / sizeof (ADUACData); i++) {
686 		ADUACData *d;
687 		d = & (uac_data [i]);
688 		if (v & d->mask) {
689 			if (string)
690 				g_string_append (string, " / ");
691 			else
692 				string = g_string_new ("");
693 			g_string_append (string, d->descr);
694 		}
695 	}
696 	if (string)
697 		return g_string_free (string, FALSE);
698 	else
699 		return NULL;
700 }
701 
702 static gchar *
ad_sam_account_type_to_string(const gchar * value)703 ad_sam_account_type_to_string (const gchar *value)
704 {
705 	gint64 i64;
706 	gchar *endptr [1];
707 
708 	if (!value || !*value)
709 		return NULL;
710 
711 	i64 = g_ascii_strtoll (value, endptr, 10);
712 	if (**endptr != '\0')
713 		return NULL;
714 	if (i64 < 0)
715 		return NULL;
716 
717 	switch (i64) {
718 	case 0x0:
719 		return g_strdup ("SAM_DOMAIN_OBJECT");
720 	case 0x10000000:
721 		return g_strdup ("SAM_GROUP_OBJECT");
722 	case 0x10000001:
723 		return g_strdup ("SAM_NON_SECURITY_GROUP_OBJECT");
724 	case 0x20000000:
725 		return g_strdup ("SAM_ALIAS_OBJECT");
726 	case 0x20000001:
727 		return g_strdup ("SAM_NON_SECURITY_ALIAS_OBJECT");
728 	case 0x30000000:
729 		return g_strdup ("SAM_NORMAL_USER_ACCOUNT");
730 	case 0x30000001:
731 		return g_strdup ("SAM_MACHINE_ACCOUNT");
732 	case 0x30000002:
733 		return g_strdup ("SAM_TRUST_ACCOUNT");
734 	case 0x40000000:
735 		return g_strdup ("SAM_APP_BASIC_GROUP");
736 	case 0x40000001:
737 		return g_strdup ("SAM_APP_QUERY_GROUP");
738 	case 0x7fffffff:
739 		return g_strdup ("SAM_ACCOUNT_TYPE_MAX");
740 	default:
741 		return NULL;
742 	}
743 }
744 
745 static void
info_fetch_cb(BrowserConnection * bcnc,gpointer out_result,EntryProperties * eprop,G_GNUC_UNUSED GError * error)746 info_fetch_cb (BrowserConnection *bcnc, gpointer out_result, EntryProperties *eprop, G_GNUC_UNUSED GError *error)
747 {
748 	if (out_result) {
749 		GtkTextBuffer *tbuffer;
750 		GtkTextIter start, end;
751 
752 		tbuffer = eprop->priv->text;
753 		gtk_text_buffer_get_start_iter (tbuffer, &start);
754 		gtk_text_buffer_get_end_iter (tbuffer, &end);
755 		gtk_text_buffer_delete (tbuffer, &start, &end);
756 
757 		GdaLdapEntry *entry = (GdaLdapEntry*) out_result;
758 		guint i;
759 		GtkTextIter current;
760 
761 		gtk_text_buffer_get_start_iter (tbuffer, &current);
762 
763 		/* DN */
764 		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, _("Distinguished Name:"), -1,
765 							  "section", NULL);
766 		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
767 		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
768 		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, entry->dn, -1,
769 							  "data", NULL);
770 		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
771 
772 		/* other attributes */
773 		const gchar *basedn;
774 		GdaDataHandler *ts_dh = NULL;
775 		basedn = browser_connection_ldap_get_base_dn (bcnc);
776 
777 		for (i = 0; i < entry->nb_attributes; i++) {
778 			GdaLdapAttribute *attr;
779 			gchar *tmp;
780 			attr = entry->attributes [i];
781 			tmp = g_strdup_printf ("%s:", attr->attr_name);
782 			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, tmp, -1, "section", NULL);
783 			g_free (tmp);
784 			gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
785 
786 			guint j;
787 			for (j = 0; j < attr->nb_values; j++) {
788 				const GValue *cvalue;
789 				cvalue = attr->values [j];
790 
791 				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1,
792 									  "starter", NULL);
793 
794 				if (G_VALUE_TYPE (cvalue) == GDA_TYPE_BINARY) {
795 					GValue *copyvalue;
796 					GtkTextTagTable *table;
797 					GtkTextTag *tag;
798 					GtkTextMark *mark;
799 
800 					copyvalue = gda_value_copy (cvalue);
801 					table = gtk_text_buffer_get_tag_table (tbuffer);
802 					tag = gtk_text_tag_new (NULL);
803 					gtk_text_tag_table_add (table, tag);
804 					g_object_set_data_full ((GObject*) tag, "binvalue",
805 								copyvalue, (GDestroyNotify) gda_value_free);
806 					g_object_unref ((GObject*) tag);
807 
808 					mark = gtk_text_buffer_create_mark (tbuffer, NULL, &current, TRUE);
809 
810 					GdkPixbuf *pixbuf;
811 					pixbuf = data_to_pixbuf (cvalue);
812 					if (pixbuf) {
813 						gtk_text_buffer_insert_pixbuf (tbuffer, &current, pixbuf);
814 						g_object_unref (pixbuf);
815 											}
816 					else {
817 						GdaDataHandler *dh;
818 						dh = gda_data_handler_get_default (G_VALUE_TYPE (cvalue));
819 						if (dh)
820 							tmp = gda_data_handler_get_str_from_value (dh, cvalue);
821 						else
822 							tmp = gda_value_stringify (cvalue);
823 						gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
824 											  tmp, -1,
825 											  "data", NULL);
826 						g_free (tmp);
827 					}
828 					GtkTextIter before;
829 					gtk_text_buffer_get_iter_at_mark (tbuffer, &before, mark);
830 					gtk_text_buffer_apply_tag (tbuffer, tag, &before, &current);
831 					gtk_text_buffer_delete_mark (tbuffer, mark);
832 
833 					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
834 										  "\n", 1,
835 										  "data", NULL);
836 				}
837 				else {
838 					GdaDataHandler *dh;
839 					dh = gda_data_handler_get_default (G_VALUE_TYPE (cvalue));
840 					if (dh)
841 						tmp = gda_data_handler_get_str_from_value (dh, cvalue);
842 					else
843 						tmp = gda_value_stringify (cvalue);
844 					if (tmp) {
845 						if (*tmp &&
846 						    ((basedn && g_str_has_suffix (tmp, basedn)) || !basedn) &&
847 						    gda_ldap_is_dn (tmp)) {
848 							/* we have a DN */
849 							GtkTextTag *tag;
850 							tag = gtk_text_buffer_create_tag (tbuffer, NULL,
851 											  "foreground", "blue",
852 											  "weight", PANGO_WEIGHT_NORMAL,
853 											  "underline", PANGO_UNDERLINE_SINGLE,
854 											  NULL);
855 							g_object_set_data_full (G_OBJECT (tag), "dn",
856 										g_strdup (tmp), g_free);
857 							gtk_text_buffer_insert_with_tags (tbuffer, &current,
858 											  tmp, -1,
859 											  tag, NULL);
860 						}
861 						else if (attr->attr_name &&
862 							 !g_ascii_strcasecmp (attr->attr_name, "objectClass")) {
863 							GtkTextTag *tag;
864 							tag = gtk_text_buffer_create_tag (tbuffer, NULL,
865 											  "foreground", "blue",
866 											  "weight", PANGO_WEIGHT_NORMAL,
867 											  "underline", PANGO_UNDERLINE_SINGLE,
868 											  NULL);
869 							g_object_set_data_full (G_OBJECT (tag), "class",
870 										g_strdup (tmp), g_free);
871 							gtk_text_buffer_insert_with_tags (tbuffer, &current,
872 											  tmp, -1,
873 											  tag, NULL);
874 						}
875 						else
876 							gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, tmp, -1,
877 												  "data", NULL);
878 
879 						gchar *extrainfo = NULL;
880 						if (!strncmp (attr->attr_name, "shadow", 6) &&
881 						    (!strcmp (attr->attr_name, "shadowLastChange") ||
882 						     !strcmp (attr->attr_name, "shadowMax") ||
883 						     !strcmp (attr->attr_name, "shadowMin") ||
884 						     !strcmp (attr->attr_name, "shadowInactive") ||
885 						     !strcmp (attr->attr_name, "shadowExpire")))
886 							extrainfo = unix_shadow_to_string (tmp, attr->attr_name);
887 						else if (!strcmp (attr->attr_name, "badPasswordTime") ||
888 							 !strcmp (attr->attr_name, "lastLogon") ||
889 							 !strcmp (attr->attr_name, "pwdLastSet") ||
890 							 !strcmp (attr->attr_name, "accountExpires") ||
891 							 !strcmp (attr->attr_name, "lockoutTime") ||
892 							 !strcmp (attr->attr_name, "lastLogonTimestamp"))
893 							extrainfo = ad_1601_timestamp_to_string (tmp, attr->attr_name);
894 						else if (!strcmp (attr->attr_name, "userAccountControl"))
895 							extrainfo = ad_1601_uac_to_string (tmp);
896 						else if (!strcmp (attr->attr_name, "sAMAccountType"))
897 							extrainfo = ad_sam_account_type_to_string (tmp);
898 
899 						if (extrainfo) {
900 							gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
901 												  " ", 1,
902 												  "data", NULL);
903 							gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
904 												  extrainfo, -1,
905 												  "convdata", NULL);
906 							g_free (extrainfo);
907 						}
908 						g_free (tmp);
909 					}
910 					else {
911 						gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, _("Can't display attribute value"), -1,
912 											  "error", NULL);
913 					}
914 					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
915 										  "\n", 1,
916 										  "data", NULL);
917 				}
918 			}
919 		}
920 		if (ts_dh)
921 			g_object_unref (ts_dh);
922 		gda_ldap_entry_free (entry);
923 	}
924 	else
925 		browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
926 				      "%s", _("Could not get information about LDAP entry"));
927 
928 	if (eprop->priv->text_search && gtk_widget_get_visible (eprop->priv->text_search))
929 		text_search_rerun (TEXT_SEARCH (eprop->priv->text_search));
930 
931 	g_object_unref (eprop);
932 }
933 
934 /**
935  * entry_properties_set_dn:
936  * @eprop: a #EntryProperties widget
937  * @dn: a DN to display information for
938  *
939  * Adjusts the display to show @dn's properties
940  */
941 void
entry_properties_set_dn(EntryProperties * eprop,const gchar * dn)942 entry_properties_set_dn (EntryProperties *eprop, const gchar *dn)
943 {
944 	g_return_if_fail (IS_ENTRY_PROPERTIES (eprop));
945 
946 	GtkTextBuffer *tbuffer;
947 	GtkTextIter start, end;
948 
949 	tbuffer = eprop->priv->text;
950 	gtk_text_buffer_get_start_iter (tbuffer, &start);
951         gtk_text_buffer_get_end_iter (tbuffer, &end);
952         gtk_text_buffer_delete (tbuffer, &start, &end);
953 
954 	if (dn && *dn) {
955 		guint id;
956 		id = browser_connection_ldap_describe_entry (eprop->priv->bcnc, dn,
957 							     (BrowserConnectionJobCallback) info_fetch_cb,
958 							     g_object_ref (eprop), NULL);
959 		if (id == 0)
960 			browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
961 					      "%s", _("Could not get information about LDAP entry"));
962 	}
963 }
964 
965 static void
show_search_bar(EntryProperties * eprop)966 show_search_bar (EntryProperties *eprop)
967 {
968 	if (! eprop->priv->text_search) {
969 		eprop->priv->text_search = text_search_new (eprop->priv->view);
970 		gtk_box_pack_start (GTK_BOX (eprop), eprop->priv->text_search, FALSE, FALSE, 0);
971 		gtk_widget_show (eprop->priv->text_search);
972 	}
973 	else {
974 		gtk_widget_show (eprop->priv->text_search);
975 		text_search_rerun (TEXT_SEARCH (eprop->priv->text_search));
976 	}
977 
978 	gtk_widget_grab_focus (eprop->priv->text_search);
979 }
980