"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "mairix.c" between
mairix-0.23.tar.gz and mairix-0.24.tar.gz

About: mairix is a program for indexing and searching email messages stored in maildir, MH or mbox folders.

mairix.c  (mairix-0.23):mairix.c  (mairix-0.24)
skipping to change at line 37 skipping to change at line 37
#include "mairix.h" #include "mairix.h"
#include "version.h" #include "version.h"
#include <assert.h> #include <assert.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <pwd.h> #include <pwd.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h> #include <ctype.h>
#include <locale.h> #include <locale.h>
#include <signal.h> #include <signal.h>
#include "imapinterface.h"
#ifdef TEST_OOM #ifdef TEST_OOM
int total_bytes=0; int total_bytes=0;
#endif #endif
int verbose = 0; int verbose = 0;
int do_hardlinks = 0; int do_hardlinks = 0;
static char *folder_base = NULL; static char *folder_base = NULL;
static char *maildir_folders = NULL; static char *maildir_folders = NULL;
static char *mh_folders = NULL; static char *mh_folders = NULL;
static char *mboxen = NULL; static char *mboxen = NULL;
static char *imap_folders = NULL;
static char *imap_pipe = NULL;
static char *imap_server = NULL;
static char *imap_username = NULL;
static char *imap_password = NULL;
static char *mfolder = NULL; static char *mfolder = NULL;
static char *omit = NULL; static char *omit = NULL;
static char *database_path = NULL; static char *database_path = NULL;
static enum folder_type output_folder_type = FT_MAILDIR; static enum folder_type output_folder_type = FT_MAILDIR;
static int skip_integrity_checks = 0; static int skip_integrity_checks = 0;
static int follow_mbox_symlinks = 0;
enum filetype { enum filetype {
M_NONE, M_FILE, M_DIR, M_OTHER M_NONE, M_FILE, M_DIR, M_OTHER
}; };
static enum filetype classify_file(char *name)/*{{{*/ static enum filetype classify_file(char *name)/*{{{*/
{ {
struct stat sb; struct stat sb;
if (stat(name, &sb) < 0) { if (stat(name, &sb) < 0) {
return M_NONE; return M_NONE;
skipping to change at line 103 skipping to change at line 110
glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_path s, &maildir_traverse_methods, omit_globs); glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_path s, &maildir_traverse_methods, omit_globs);
break; break;
case FT_MH: case FT_MH:
glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_path s, &mh_traverse_methods, omit_globs); glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_path s, &mh_traverse_methods, omit_globs);
break; break;
case FT_MBOX: case FT_MBOX:
glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_path s, &mbox_traverse_methods, omit_globs); glob_and_expand_paths(folder_base, raw_paths, n_raw_paths, &paths, &n_path s, &mbox_traverse_methods, omit_globs);
break; break;
case FT_RAW: /* cannot happen but to keep compiler happy */ case FT_RAW: /* cannot happen but to keep compiler happy */
case FT_EXCERPT: case FT_EXCERPT:
case FT_IMAP:
break; break;
} }
for (i=0; i<n_paths; i++) { for (i=0; i<n_paths; i++) {
struct stat mfolder_sb, src_folder_sb; /* for checking inode numbers */ struct stat mfolder_sb, src_folder_sb; /* for checking inode numbers */
/* if the complete path names are the same, definitely a match */ /* if the complete path names are the same, definitely a match */
if (strcmp (complete_mfolder, paths[i]) == 0) if (strcmp (complete_mfolder, paths[i]) == 0)
return 1; return 1;
/* also a match if they point to the same file or directory but /* also a match if they point to the same file or directory but
via different routes (e.g. absolute path for one but path with via different routes (e.g. absolute path for one but path with
skipping to change at line 171 skipping to change at line 179
if (!strncasecmp(temp, "mh", 2)) { if (!strncasecmp(temp, "mh", 2)) {
output_folder_type = FT_MH; output_folder_type = FT_MH;
} else if (!strncasecmp(temp, "maildir", 7)) { } else if (!strncasecmp(temp, "maildir", 7)) {
output_folder_type = FT_MAILDIR; output_folder_type = FT_MAILDIR;
} else if (!strncasecmp(temp, "raw", 3)) { } else if (!strncasecmp(temp, "raw", 3)) {
output_folder_type = FT_RAW; output_folder_type = FT_RAW;
} else if (!strncasecmp(temp, "excerpt", 3)) { } else if (!strncasecmp(temp, "excerpt", 3)) {
output_folder_type = FT_EXCERPT; output_folder_type = FT_EXCERPT;
} else if (!strncasecmp(temp, "mbox", 4)) { } else if (!strncasecmp(temp, "mbox", 4)) {
output_folder_type = FT_MBOX; output_folder_type = FT_MBOX;
} else if (!strncasecmp(temp, "imap", 4)) {
output_folder_type = FT_IMAP;
} }
else { else {
fprintf(stderr, "Unrecognized mformat <%s>\n", temp); fprintf(stderr, "Unrecognized mformat <%s>\n", temp);
} }
free(temp); free(temp);
} }
/*}}}*/ /*}}}*/
static void parse_rc_file(char *name)/*{{{*/ static void parse_rc_file(char *name)/*{{{*/
{ {
FILE *in; FILE *in;
skipping to change at line 252 skipping to change at line 262
fprintf(stderr, "'folders=' option in rc file is depracated, use 'maildir= '\n"); fprintf(stderr, "'folders=' option in rc file is depracated, use 'maildir= '\n");
add_folders(&maildir_folders, copy_value(p)); add_folders(&maildir_folders, copy_value(p));
} }
else if (!strncasecmp(p, "maildir=", 8)) add_folders(&maildir_folders, copy_ value(p)); else if (!strncasecmp(p, "maildir=", 8)) add_folders(&maildir_folders, copy_ value(p));
else if (!strncasecmp(p, "mh_folders=", 11)) { else if (!strncasecmp(p, "mh_folders=", 11)) {
fprintf(stderr, "'mh_folders=' option in rc file is depracated, use 'mh='\ n"); fprintf(stderr, "'mh_folders=' option in rc file is depracated, use 'mh='\ n");
add_folders(&mh_folders, copy_value(p)); add_folders(&mh_folders, copy_value(p));
} }
else if (!strncasecmp(p, "mh=", 3)) add_folders(&mh_folders, copy_value(p)); else if (!strncasecmp(p, "mh=", 3)) add_folders(&mh_folders, copy_value(p));
else if (!strncasecmp(p, "mbox=", 5)) add_folders(&mboxen, copy_value(p)); else if (!strncasecmp(p, "mbox=", 5)) add_folders(&mboxen, copy_value(p));
else if (!strncasecmp(p, "imap=", 5)) add_folders(&imap_folders, copy_value(
p));
else if (!strncasecmp(p, "imap_pipe=", 10)) imap_pipe = copy_value(p);
else if (!strncasecmp(p, "imap_server=", 12)) imap_server = copy_value(p);
else if (!strncasecmp(p, "imap_username=", 14)) imap_username = copy_value(p
);
else if (!strncasecmp(p, "imap_password=", 14)) imap_password = copy_value(p
);
else if (!strncasecmp(p, "follow_mbox_symlinks", 20)) follow_mbox_symlinks =
1;
else if (!strncasecmp(p, "omit=", 5)) add_folders(&omit, copy_value(p)); else if (!strncasecmp(p, "omit=", 5)) add_folders(&omit, copy_value(p));
else if (!strncasecmp(p, "mformat=", 8)) { else if (!strncasecmp(p, "mformat=", 8)) {
parse_output_folder(p); parse_output_folder(p);
} }
else if (!strncasecmp(p, "mfolder=", 8)) mfolder = copy_value(p); else if (!strncasecmp(p, "mfolder=", 8)) mfolder = copy_value(p);
else if (!strncasecmp(p, "database=", 9)) database_path = copy_value(p); else if (!strncasecmp(p, "database=", 9)) database_path = copy_value(p);
else if (!strncasecmp(p, "nochecks", 8)) skip_integrity_checks = 1; else if (!strncasecmp(p, "nochecks", 8)) skip_integrity_checks = 1;
else { else {
if (verbose) { if (verbose) {
fprintf(stderr, "Unrecognized option at line %d in %s\n", lineno, name); fprintf(stderr, "Unrecognized option at line %d in %s\n", lineno, name);
} }
} }
} }
fclose(in); fclose(in);
if (used_default_name) free(name); if (used_default_name) free(name);
} }
/*}}}*/ /*}}}*/
static int message_compare(const void *a, const void *b)/*{{{*/
{
/* FIXME : Is this a sensible way to do this with mbox messages in the picture
? */
struct msgpath *aa = (struct msgpath *) a;
struct msgpath *bb = (struct msgpath *) b;
if (aa->type < bb->type) return -1;
if (aa->type > bb->type) return 1;
return strcmp(aa->src.mpf.path, bb->src.mpf.path);
}
/*}}}*/
static void sort_message_list(struct msgpath_array *arr)/*{{{*/
{
qsort(arr->paths, arr->n, sizeof(struct msgpath), message_compare);
}
/*}}}*/
static int compare_strings(const void *a, const void *b)/*{{{*/ static int compare_strings(const void *a, const void *b)/*{{{*/
{ {
const char **aa = (const char **) a; const char **aa = (const char **) a;
const char **bb = (const char **) b; const char **bb = (const char **) b;
return strcmp(*aa, *bb); return strcmp(*aa, *bb);
} }
/*}}}*/ /*}}}*/
static int check_message_list_for_duplicates(struct msgpath_array *msgs)/*{{{*/ static int check_message_list_for_duplicates(struct msgpath_array *msgs)/*{{{*/
{ {
/* Caveat : only examines the file-per-message case */ /* Caveat : only examines the file-per-message case */
char **sorted_paths; char **sorted_paths, **sorted_imap;
int i, n, nn; int i, n, nn, imap_nn;
int result; int result;
n = msgs->n; n = msgs->n;
sorted_paths = new_array(char *, n); sorted_paths = new_array(char *, n);
for (i=0, nn=0; i<n; i++) { sorted_imap = new_array(char *, n);
switch (msgs->type[i]) { for (i=0, nn=0, imap_nn=0; i<n; i++) {
switch (msgs->paths[i].type) {
case MTY_MBOX: case MTY_MBOX:
break; break;
case MTY_DEAD: case MTY_DEAD:
assert(0); assert(0);
break; break;
case MTY_FILE: case MTY_FILE:
sorted_paths[nn++] = msgs->paths[i].src.mpf.path; sorted_paths[nn++] = msgs->paths[i].src.mpf.path;
break; break;
case MTY_IMAP:
sorted_imap[imap_nn++] = msgs->paths[i].src.mpf.path;
break;
} }
} }
qsort(sorted_paths, nn, sizeof(char *), compare_strings); qsort(sorted_paths, nn, sizeof(char *), compare_strings);
qsort(sorted_imap, imap_nn, sizeof(char *), compare_strings);
result = 0; result = 0;
for (i=1; i<nn; i++) { for (i=1; i<nn; i++) {
if (!strcmp(sorted_paths[i-1], sorted_paths[i])) { if (!strcmp(sorted_paths[i-1], sorted_paths[i])) {
result = 1; result = 1;
break; break;
} }
} }
for (i=1; i<imap_nn; i++) {
if (!strcmp(sorted_imap[i-1], sorted_imap[i])) {
result = 1;
break;
}
}
free(sorted_paths); free(sorted_paths);
free(sorted_imap);
return result; return result;
} }
/*}}}*/ /*}}}*/
static void emit_int(int x)/*{{{*/ static void emit_int(int x)/*{{{*/
{ {
char buf1[20], buf2[20]; char buf1[20], buf2[20];
char *p, *q; char *p, *q;
int neg=0; int neg=0;
p = buf1; p = buf1;
skipping to change at line 354 skipping to change at line 399
int filelen; int filelen;
char *p; char *p;
static char msg1[] = "Out of memory (at "; static char msg1[] = "Out of memory (at ";
static char msg2[] = " bytes)\n"; static char msg2[] = " bytes)\n";
/* Perhaps even strlen is unsafe in this situation? */ /* Perhaps even strlen is unsafe in this situation? */
p = file; p = file;
while (*p) p++; while (*p) p++;
filelen = p - file; filelen = p - file;
write(2, msg1, sizeof(msg1)); write(2, msg1, sizeof(msg1)-1);
write(2, file, filelen); write(2, file, filelen);
write(2, ":", 1); write(2, ":", 1);
emit_int(line); emit_int(line);
write(2, ", ", 2); write(2, ", ", 2);
emit_int(size); emit_int(size);
write(2, msg2, sizeof(msg2)); write(2, msg2, sizeof(msg2)-1);
exit(2); exit(2);
} }
/*}}}*/ /*}}}*/
void report_error(const char *str, const char *filename)/*{{{*/ void report_error(const char *str, const char *filename)/*{{{*/
{ {
if (filename) { if (filename) {
int len = strlen(str) + strlen(filename) + 4; int len = strlen(str) + strlen(filename) + 4;
char *t; char *t;
t = new_array(char, len); t = new_array(char, len);
sprintf(t, "%s '%s'", str, filename); sprintf(t, "%s '%s'", str, filename);
skipping to change at line 493 skipping to change at line 538
int do_purge = 0; int do_purge = 0;
int any_updates = 0; int any_updates = 0;
int any_purges = 0; int any_purges = 0;
int do_help = 0; int do_help = 0;
int do_raw_output = 0; int do_raw_output = 0;
int do_excerpt_output = 0; int do_excerpt_output = 0;
int do_dump = 0; int do_dump = 0;
int do_integrity_checks = 1; int do_integrity_checks = 1;
int do_forced_unlock = 0; int do_forced_unlock = 0;
int do_fast_index = 0; int do_fast_index = 0;
int do_mbox_symlinks = 0;
struct imap_ll *imapc = NULL;
unsigned int forced_hash_key = CREATE_RANDOM_DATABASE_HASH; unsigned int forced_hash_key = CREATE_RANDOM_DATABASE_HASH;
struct globber_array *omit_globs; struct globber_array *omit_globs;
int result; int result;
setlocale(LC_CTYPE, ""); setlocale(LC_CTYPE, "");
while (++argv, --argc) { while (++argv, --argc) {
skipping to change at line 561 skipping to change at line 608
exit(1); exit(1);
} }
} else if (!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose")) { } else if (!strcmp(*argv, "-v") || !strcmp(*argv, "--verbose")) {
verbose = 1; verbose = 1;
} else if (!strcmp(*argv, "-V") || !strcmp(*argv, "--version")) { } else if (!strcmp(*argv, "-V") || !strcmp(*argv, "--version")) {
print_version(); print_version();
exit(0); exit(0);
} else if (!strcmp(*argv, "-h") || } else if (!strcmp(*argv, "-h") ||
!strcmp(*argv, "--help")) { !strcmp(*argv, "--help")) {
do_help = 1; do_help = 1;
} else if ((*argv)[0] == '-') {
fprintf(stderr, "Unrecognized option %s\n", *argv);
} else if (!strcmp(*argv, "--")) { } else if (!strcmp(*argv, "--")) {
/* End of args */ /* End of args */
argc--;
argv++;
break; break;
} else if ((*argv)[0] == '-') {
fprintf(stderr, "Unrecognized option %s\n", *argv);
exit(3);
} else { } else {
/* standard args start */ /* standard args start */
break; break;
} }
} }
if (do_help) { if (do_help) {
usage(); usage();
exit(0); exit(0);
} }
skipping to change at line 620 skipping to change at line 670
} }
if (arg_mfolder) { if (arg_mfolder) {
mfolder = arg_mfolder; mfolder = arg_mfolder;
} }
if (skip_integrity_checks) { if (skip_integrity_checks) {
do_integrity_checks = 0; do_integrity_checks = 0;
} }
if (follow_mbox_symlinks) {
do_mbox_symlinks = 1;
}
if (!folder_base) { if (!folder_base) {
fprintf(stderr, "No folder_base/MAIRIX_FOLDER_BASE set\n"); fprintf(stderr, "No folder_base/MAIRIX_FOLDER_BASE set\n");
exit(2); exit(2);
} }
if (!database_path) { if (!database_path) {
fprintf(stderr, "No database/MAIRIX_DATABASE set\n"); fprintf(stderr, "No database/MAIRIX_DATABASE set\n");
exit(2); exit(2);
} }
skipping to change at line 674 skipping to change at line 728
case FT_RAW: case FT_RAW:
case FT_EXCERPT: case FT_EXCERPT:
break; break;
default: default:
fprintf(stderr, "No mfolder/MAIRIX_MFOLDER set\n"); fprintf(stderr, "No mfolder/MAIRIX_MFOLDER set\n");
unlock_and_exit(2); unlock_and_exit(2);
} }
mfolder = new_string(""); mfolder = new_string("");
} }
/* complete_mfolder is needed by search_top() and member_of() so if (output_folder_type == FT_IMAP) {
compute it once here rather than in search_top() as well */
if ((mfolder[0] == '/') ||
((mfolder[0] == '.') && (mfolder[1] == '/'))) {
complete_mfolder = new_string(mfolder); complete_mfolder = new_string(mfolder);
} else { } else {
len = strlen(folder_base) + strlen(mfolder) + 2; /* complete_mfolder is needed by search_top() and member_of() so
complete_mfolder = new_array(char, len); compute it once here rather than in search_top() as well */
strcpy(complete_mfolder, folder_base); if ((mfolder[0] == '/') ||
strcat(complete_mfolder, "/"); ((mfolder[0] == '.') && (mfolder[1] == '/'))) {
strcat(complete_mfolder, mfolder); complete_mfolder = new_string(mfolder);
} else {
len = strlen(folder_base) + strlen(mfolder) + 2;
complete_mfolder = new_array(char, len);
strcpy(complete_mfolder, folder_base);
strcat(complete_mfolder, "/");
strcat(complete_mfolder, mfolder);
}
} }
/* check whether mfolder output would destroy a mail folder or mbox */ /* check whether mfolder output would destroy a mail folder or mbox */
switch (output_folder_type) { switch (output_folder_type) {
case FT_RAW: case FT_RAW:
case FT_EXCERPT: case FT_EXCERPT:
break; break;
case FT_IMAP:
/* the same check as below could be implemented in the future */
break;
default: default:
if ((member_of(complete_mfolder,folder_base, maildir_folders, FT_MAILDIR , omit_globs)|| if ((member_of(complete_mfolder,folder_base, maildir_folders, FT_MAILDIR , omit_globs)||
member_of (complete_mfolder, folder_base, mh_folders, FT_MH, omit_g lobs) || member_of (complete_mfolder, folder_base, mh_folders, FT_MH, omit_g lobs) ||
member_of (complete_mfolder, folder_base, mboxen, FT_MBOX, omit_glo bs))) { member_of (complete_mfolder, folder_base, mboxen, FT_MBOX, omit_glo bs))) {
fprintf (stderr, fprintf (stderr,
"You asked search results to go to the folder '%s'.\n" "You asked search results to go to the folder '%s'.\n"
"That folder appears to be one of the indexed mail folders!\n" "That folder appears to be one of the indexed mail folders!\n"
"For your own good, I refuse to output search results to an indexe d mail folder.\n", "For your own good, I refuse to output search results to an indexe d mail folder.\n",
mfolder); mfolder);
unlock_and_exit(3); unlock_and_exit(3);
} }
} }
ftype = classify_file(database_path); ftype = classify_file(database_path);
if (ftype != M_FILE) { if (ftype != M_FILE) {
fprintf(stderr, "No database file '%s' is present.\nYou need to do an inde xing run first.\n", fprintf(stderr, "No database file '%s' is present.\nYou need to do an inde xing run first.\n",
database_path); database_path);
unlock_and_exit(3); unlock_and_exit(3);
} }
result = search_top(do_threads, do_augment, database_path, complete_mfolder, argv, output_folder_type, verbose); result = search_top(do_threads, do_augment, database_path, complete_mfolder, argv, output_folder_type, verbose, imap_pipe, imap_server, imap_username, imap_ password);
} else { } else {
enum filetype ftype; enum filetype ftype;
if (!maildir_folders && !mh_folders && !mboxen) { if (imap_pipe && imap_server) {
fprintf(stderr, "No [mh_]folders/mboxen/MAIRIX_[MH_]FOLDERS set\n"); fprintf(stderr, "specify one of imap_pipe or imap_server, not both\n");
unlock_and_exit(2);
}
if (imap_folders && (!(imap_pipe || imap_server))) {
fprintf(stderr, "If imap is given, imap_pipe OR imap_server is required\n"
);
imap_folders = NULL;
}
if (!maildir_folders && !mh_folders && !mboxen && !imap_folders) {
fprintf(stderr, "No [mh_]folders/mboxen/imap/MAIRIX_[MH_]FOLDERS set\n");
unlock_and_exit(2); unlock_and_exit(2);
} }
if (verbose) printf("Finding all currently existing messages...\n"); if (verbose) printf("Finding all currently existing messages...\n");
msgs = new_msgpath_array(); msgs = new_msgpath_array();
if (imap_folders) {
imapc = imap_start(imap_pipe, imap_server, imap_username, imap_password);
if (!imapc) unlock_and_exit(2);
build_imap_message_list(imap_folders, msgs, omit_globs, imapc);
}
if (maildir_folders) { if (maildir_folders) {
build_message_list(folder_base, maildir_folders, FT_MAILDIR, msgs, omit_gl obs); build_message_list(folder_base, maildir_folders, FT_MAILDIR, msgs, omit_gl obs);
} }
if (mh_folders) { if (mh_folders) {
build_message_list(folder_base, mh_folders, FT_MH, msgs, omit_globs); build_message_list(folder_base, mh_folders, FT_MH, msgs, omit_globs);
} }
sort_message_list(msgs);
/* The next call sorts the msgs array as part of looking for duplicates. */ /* The next call sorts the msgs array as part of looking for duplicates. */
if (check_message_list_for_duplicates(msgs)) { if (check_message_list_for_duplicates(msgs)) {
fprintf(stderr, "Message list contains duplicates - check your 'folders' s etting\n"); fprintf(stderr, "Message list contains duplicates - check your 'folders' s etting\n");
unlock_and_exit(2); unlock_and_exit(2);
} }
/* Try to open existing database */ /* Try to open existing database */
ftype = classify_file(database_path); ftype = classify_file(database_path);
if (ftype == M_FILE) { if (ftype == M_FILE) {
skipping to change at line 749 skipping to change at line 826
db = new_database_from_file(database_path, do_integrity_checks); db = new_database_from_file(database_path, do_integrity_checks);
if (verbose) printf("Loaded %d existing messages\n", db->n_msgs); if (verbose) printf("Loaded %d existing messages\n", db->n_msgs);
} else if (ftype == M_NONE) { } else if (ftype == M_NONE) {
if (verbose) printf("Starting new database\n"); if (verbose) printf("Starting new database\n");
db = new_database( forced_hash_key ); db = new_database( forced_hash_key );
} else { } else {
fprintf(stderr, "database path %s is not a file; you can't put the databas e there\n", database_path); fprintf(stderr, "database path %s is not a file; you can't put the databas e there\n", database_path);
unlock_and_exit(2); unlock_and_exit(2);
} }
build_mbox_lists(db, folder_base, mboxen, omit_globs); build_mbox_lists(db, folder_base, mboxen, omit_globs, do_mbox_symlinks);
any_updates = update_database(db, msgs->paths, msgs->n, do_fast_index); any_updates = update_database(db, msgs->paths, msgs->n, do_fast_index, imapc );
if (do_purge) { if (do_purge) {
any_purges = cull_dead_messages(db, do_integrity_checks); any_purges = cull_dead_messages(db, do_integrity_checks);
} }
if (any_updates || any_purges) { if (any_updates || any_purges) {
/* For now write it every time. This is obviously the most reliable metho d. */ /* For now write it every time. This is obviously the most reliable metho d. */
write_database(db, database_path, do_integrity_checks); write_database(db, database_path, do_integrity_checks);
} }
#if 0 #if 0
get_db_stats(db); get_db_stats(db);
 End of changes. 29 change blocks. 
22 lines changed or deleted 105 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)