diff options
Diffstat (limited to 'csvncgi/ui-tree.c')
-rw-r--r-- | csvncgi/ui-tree.c | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/csvncgi/ui-tree.c b/csvncgi/ui-tree.c new file mode 100644 index 0000000..bc46535 --- /dev/null +++ b/csvncgi/ui-tree.c @@ -0,0 +1,685 @@ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysinfo.h> +#include <sys/types.h> +#ifdef HAVE_INTTYPES_H +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#include <stddef.h> /* offsetof(3) */ +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <limits.h> +#include <string.h> /* strdup(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <locale.h> +#include <unistd.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> + +#include <nls.h> + +#include <defs.h> + +#include <fatal.h> +#include <http.h> +#include <html.h> + +#include <cmpvers.h> +#include <dlist.h> +#include <strbuf.h> +#include <repolist.h> +#include <wrapper.h> +#include <system.h> +#include <date.h> + +#include <ctx.h> +#include <ui-shared.h> + + +/******************************** + Sorted dlist functions: + */ +struct tree_line { + char *name; + char *revision; + char *date; + char *size; +}; + +static struct dlist *directories = NULL; +static struct dlist *files = NULL; + +static struct tree_line *tree_line_alloc( const char *name, const char *revision, const char *date, const char *size ) +{ + struct tree_line *line = NULL; + line = (struct tree_line *)xmalloc( sizeof(struct tree_line) ); + + if( name ) { line->name = xstrdup( name ); } + if( revision ) { line->revision = xstrdup( revision ); } + if( date ) { line->date = xstrdup( date ); } + if( size ) { line->size = xstrdup( size ); } + + return line; +} + +static void __line_free( void *data, void *user_data ) +{ + struct tree_line *line = (struct tree_line *)data; + if( line ) + { + if( line->name ) { free( line->name ); line->name = NULL; } + if( line->revision ) { free( line->revision ); line->revision = NULL; } + if( line->date ) { free( line->date ); line->date = NULL; } + if( line->size ) { free( line->size ); line->size = NULL; } + + free( line ); + } +} + +static struct dlist *tree_lines_free( struct dlist *lines ) +{ + if( lines ) + { + dlist_free( lines, __line_free ); + lines = NULL; + } + return lines; +} + +static int __cmp_tree_lines_bytag( const void *a, const void *b ) +{ + char *v1 = NULL, *v2 = NULL; + int ret = -1; + + struct tree_line *line1 = (struct tree_line *)a; + struct tree_line *line2 = (struct tree_line *)b; + + if( line1->name && line2->name ) + { + v1 = line1->name; + v2 = line2->name; + while( *v1 && !isdigit( *v1 ) ) ++v1; + while( *v2 && !isdigit( *v2 ) ) ++v2; + ret = strcmp( line1->name, line2->name ); + } + else + return ret; + + if( !*v1 || !*v2 ) + { + return ret; + } + + /****************************************** + sort reversive to show newest tag first: + */ + return -cmp_version( (const char *)v1, (const char *)v2 ); +} + +static struct dlist *tree_lines_sort_bytag( struct dlist *lines ) +{ + if( lines ) { lines = dlist_sort( lines, __cmp_tree_lines_bytag ); } + return lines; +} +/* + End of sorted dlist functions. + ********************************/ + + +static const char *is_file_executable( const char *relative_path, const char *name, int revision ) +{ + const char *co_prefix = ctx.repo.checkout_ro_prefix; + const char *repo_path = ctx.repo.name; + const char *ret = ""; + char *path = NULL; + + if( !name ) return ret; + + if( relative_path && *relative_path ) + { + path = (char *)xmalloc( strlen( relative_path ) + 2 ); + path[0] = '/'; + sprintf( (char *)&path[1], relative_path ); + } + else + { + path = (char *)xmalloc( 1 ); + path[0] = '\0'; + } + + if( co_prefix ) + { + char cmd[1024]; + struct strbuf buf = STRBUF_INIT; + pid_t p = (pid_t) -1; + int rc; + + if( revision ) + snprintf( (char *)&cmd[0], 1024, + "svn propget svn:executable --revision %d %s/%s%s/%s 2>/dev/null", + revision, co_prefix, repo_path, path, name ); + else + snprintf( (char *)&cmd[0], 1024, + "svn propget svn:executable %s/%s%s/%s 2>/dev/null", + co_prefix, repo_path, path, name ); + p = sys_exec_command( &buf, cmd ); + rc = sys_wait_command( p, NULL ); + if( rc != 0 ) + { + strbuf_release( &buf ); + free( path ); + return ""; + } + + strbuf_trim( &buf ); + if( buf.buf[0] == '*' ) + ret = " exec"; + strbuf_release( &buf ); + } + + free( path ); + return ret; +} + + +static xmlNode *xml_find_node_by_name( xmlNode *root, const char *name ) +{ + xmlNode *node = NULL; + xmlNode *ret = NULL; + + if( !name || !*name || !root || !root->children ) return ret; + + for( node = root->children; node; node = node->next ) + { + if( node->type == XML_ELEMENT_NODE ) + { + if( !strcmp( (char *)name, (char *)node->name ) ) + { + return node; + } + } + } + return ret; +} + +static void xml_csvn_tree( const struct strbuf *buf ) +{ + xmlDocPtr doc = NULL; + xmlNode *root = NULL; + + if( !buf || !buf->buf ) return; + + LIBXML_TEST_VERSION + + doc = xmlReadMemory( buf->buf, buf->len, "list.xml", NULL, 0 ); + if( !doc ) + { + html_fatal( "cannot parse svn list.xml" ); + return; + } + + root = xmlDocGetRootElement( doc ); + if( !root ) + { + xmlFreeDoc( doc ); + xmlCleanupParser(); + return; + } + + if( !strcmp( "lists", (char *)root->name ) ) + { + xmlNode *list = NULL, *node = NULL; + + list = xml_find_node_by_name( root, "list" ); + if( !list ) + { + xmlFreeDoc( doc ); + xmlCleanupParser(); + return; + } + + /**************************** + Collect directories first: + */ + for( node = list->children; node; node = node->next ) + { + if( node->type == XML_ELEMENT_NODE && !strcmp( "entry", (char *)node->name ) ) + { + xmlChar *content = NULL; + xmlNode *param = NULL; + + content = xmlGetProp( node, (const unsigned char *)"kind" ); + if( !strcmp( (const char *)content, "dir" ) ) + { + xmlChar *name = NULL, *revision = NULL, *date = NULL; + + for( param = node->children; param; param= param->next ) + { + if( param->type == XML_ELEMENT_NODE && !strcmp( "name", (char *)param->name ) ) + { + name = xmlNodeGetContent( param ); + } + if( param->type == XML_ELEMENT_NODE && !strcmp( "commit", (char *)param->name ) ) + { + xmlNode *commit_date = NULL; + revision = xmlGetProp( param, (const unsigned char *)"revision" ); + commit_date = xml_find_node_by_name( param, "date" ); + if( commit_date ) + date = xmlNodeGetContent( commit_date ); + } + } /* End for entry parameters */ + + if( name && revision && date ) + { + struct tree_line *line = NULL; + + line = tree_line_alloc( (const char *)name, (const char *)revision, (const char *)date, NULL ); + directories = dlist_append( directories, (void *)line ); + + xmlFree( name ); + xmlFree( revision ); + xmlFree( date ); + } + else + { + if( name ) xmlFree( name ); + if( revision ) xmlFree( revision ); + if( date ) xmlFree( date ); + } + } + xmlFree( content ); + } + } + + /**************************** + Collect files: + */ + for( node = list->children; node; node = node->next ) + { + if( node->type == XML_ELEMENT_NODE && !strcmp( "entry", (char *)node->name ) ) + { + xmlChar *content = NULL; + xmlNode *param = NULL; + + content = xmlGetProp( node, (const unsigned char *)"kind" ); + if( !strcmp( (const char *)content, "file" ) ) + { + xmlChar *name = NULL, *size = NULL, *revision = NULL, *date = NULL; + + for( param = node->children; param; param= param->next ) + { + if( param->type == XML_ELEMENT_NODE && !strcmp( "name", (char *)param->name ) ) + { + name = xmlNodeGetContent( param ); + } + if( param->type == XML_ELEMENT_NODE && !strcmp( "size", (char *)param->name ) ) + { + size = xmlNodeGetContent( param ); + } + if( param->type == XML_ELEMENT_NODE && !strcmp( "commit", (char *)param->name ) ) + { + xmlNode *commit_date = NULL; + revision = xmlGetProp( param, (const unsigned char *)"revision" ); + commit_date = xml_find_node_by_name( param, "date" ); + if( commit_date ) + date = xmlNodeGetContent( commit_date ); + } + } /* End for entry parameters */ + + if( name && size && revision && date ) + { + struct tree_line *line = NULL; + + line = tree_line_alloc( (const char *)name, (const char *)revision, (const char *)date, (const char *)size ); + files = dlist_append( files, (void *)line ); + + xmlFree( name ); + xmlFree( size ); + xmlFree( revision ); + xmlFree( date ); + } + else + { + if( name ) xmlFree( name ); + if( size ) xmlFree( size ); + if( revision ) xmlFree( revision ); + if( date ) xmlFree( date ); + } + } + xmlFree( content ); + } + } + } + + xmlFreeDoc(doc); + xmlCleanupParser(); +} + + +static void dlist_csvn_tree( struct strbuf *sb, const char *relative_path, int rev ) +{ + char *path = NULL; + + if( !sb || !sb->len ) return; + + if( relative_path && *relative_path ) + { + path = (char *)xmalloc( strlen( relative_path ) + 2 ); + path[0] = '/'; + sprintf( (char *)&path[1], relative_path ); + } + else + { + path = (char *)xmalloc( 1 ); + path[0] = '\0'; + } + + if( path && *path && !strcmp( (char *)&path[1], ctx.repo.tags ) ) + { + directories = tree_lines_sort_bytag( directories ); + } + + strbuf_addstr( sb, "\n" ); + strbuf_addf( sb, " <div class=\"repo-tree-header\">\n" ); + strbuf_addf( sb, " <div class=\"row\">\n" ); + strbuf_addf( sb, " <div class=\"col-path\">Path</div>\n" ); + strbuf_addf( sb, " <div class=\"col-size\"><div class=\"tree-size trunc\">Size</div></div>\n" ); + strbuf_addf( sb, " <div class=\"col-rev\"><div class=\"tree-rev trunc\">Rev</div></div>\n" ); + strbuf_addf( sb, " <div class=\"col-date\"><div class=\"tree-date trunc\">Date</div></div>\n" ); + strbuf_addf( sb, " <div class=\"col-links\"><div class=\"tree-links trunc\">Links</div></div>\n" ); + strbuf_addf( sb, " </div>\n" ); + strbuf_addf( sb, " </div>\n\n" ); + + strbuf_addf( sb, " <div class=\"tree\">\n\n" ); + + /************************** + Print directories first: + */ + if( directories ) + { + struct dlist *list = directories; + while( list ) + { + struct tree_line *line = (struct tree_line *)list->data; + + if( line->name && line->revision && line->date ) + { + struct tm tm; + time_t time = -1; + const char *query_string = ctx_remove_query_param( ctx.env.query_string, "rev" ); + + query_string = ctx_remove_query_param( query_string, "op" ); + + time = parse_date( &tm, (const char *)line->date ); + + strbuf_addf( sb, " <div class=\"row\">\n" ); + if( ctx.env.query_string && *ctx.env.query_string ) + strbuf_addf( sb, " <div class=\"col-path\"><a href=\"/%s%s/%s/?%s\"><div class=\"tree-path dir\">%s/</div></a></div>\n", ctx.repo.name, path, line->name, ctx.env.query_string, line->name ); + else + strbuf_addf( sb, " <div class=\"col-path\"><a href=\"/%s%s/%s/\"><div class=\"tree-path dir\">%s/</div></a></div>\n", ctx.repo.name, path, line->name, line->name ); + strbuf_addf( sb, " <div class=\"col-size\"><div onclick=\"trunc(this)\" class=\"tree-size trunc\">4096</div></div>\n" ); + strbuf_addf( sb, " <div class=\"col-rev\"><div onclick=\"trunc(this)\" class=\"tree-rev trunc\">%s</div></div>\n", line->revision ); + if( time != -1 ) + { + strbuf_addf( sb, " <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">" ); + csvn_print_age( sb, time, 0, 0 ); + strbuf_addf( sb, "</div></div>\n" ); + } + else + { + strbuf_addf( sb, " <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">unknown</div></div>\n" ); + } + strbuf_addf( sb, " <div class=\"col-links\">\n" ); + strbuf_addf( sb, " <div onclick=\"trunc(this)\" class=\"tree-links trunc\">\n" ); + if( query_string && *query_string ) + { + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=log&rev=%s&%s\">log</a> \n", ctx.repo.name, path, line->name, line->revision, query_string ); + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=diff&rev=%s&%s\">diff</a>\n", ctx.repo.name, path, line->name, line->revision, query_string ); + } + else + { + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=log&rev=%s\">log</a> \n", ctx.repo.name, path, line->name, line->revision ); + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=diff&rev=%s\">diff</a>\n", ctx.repo.name, path, line->name, line->revision ); + } + strbuf_addf( sb, " </div>\n" ); + strbuf_addf( sb, " </div>\n" ); + strbuf_addf( sb, " </div>\n\n" ); + } + + list = dlist_next( list ); + } + directories = tree_lines_free( directories ); + } + + /************************** + Print files: + */ + if( files ) + { + struct dlist *list = files; + while( list ) + { + struct tree_line *line = (struct tree_line *)list->data; + + if( line->name && line->size && line->revision && line->date ) + { + struct tm tm; + time_t time = -1; + const char *query_string = ctx_remove_query_param( ctx.env.query_string, "rev" ); + const char *exec = is_file_executable( relative_path, (const char *)line->name, rev ); + + query_string = ctx_remove_query_param( query_string, "op" ); + + time = parse_date( &tm, (const char *)line->date ); + + strbuf_addf( sb, " <div class=\"row\">\n" ); + if( ctx.env.query_string && *ctx.env.query_string ) + strbuf_addf( sb, " <div class=\"col-path\"><a href=\"/%s%s/%s/?%s\"><div class=\"tree-path file%s\">%s</div></a></div>\n", ctx.repo.name, path, line->name, ctx.env.query_string, exec, line->name ); + else + strbuf_addf( sb, " <div class=\"col-path\"><a href=\"/%s%s/%s/\"><div class=\"tree-path file%s\">%s</div></a></div>\n", ctx.repo.name, path, line->name, exec, line->name ); + strbuf_addf( sb, " <div class=\"col-size\"><div onclick=\"trunc(this)\" class=\"tree-size trunc\">%s</div></div>\n", line->size ); + strbuf_addf( sb, " <div class=\"col-rev\"><div onclick=\"trunc(this)\" class=\"tree-rev trunc\">%s</div></div>\n", line->revision ); + if( time != -1 ) + { + strbuf_addf( sb, " <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">" ); + csvn_print_age( sb, time, 0, 0 ); + strbuf_addf( sb, "</div></div>\n" ); + } + else + { + strbuf_addf( sb, " <div class=\"col-date\"><div onclick=\"trunc(this)\" class=\"tree-date trunc\">unknown</div></div>\n" ); + } + strbuf_addf( sb, " <div class=\"col-links\">\n" ); + strbuf_addf( sb, " <div onclick=\"trunc(this)\" class=\"tree-links trunc\">\n" ); + if( query_string && *query_string ) + { + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=log&rev=%s&%s\">log</a> \n", ctx.repo.name, path, line->name, line->revision, query_string ); + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=diff&rev=%s&%s\">diff </a>\n", ctx.repo.name, path, line->name, line->revision, query_string ); + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=blame&rev=%s&%s\">blame</a>\n", ctx.repo.name, path, line->name, line->revision, query_string ); + } + else + { + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=log&rev=%s\">log</a> \n", ctx.repo.name, path, line->name, line->revision ); + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=diff&rev=%s\">diff</a> \n", ctx.repo.name, path, line->name, line->revision ); + strbuf_addf( sb, " <a href=\"/%s%s/%s/?op=blame&rev=%s\">blame</a>\n", ctx.repo.name, path, line->name, line->revision ); + } + strbuf_addf( sb, " </div>\n" ); + strbuf_addf( sb, " </div>\n" ); + strbuf_addf( sb, " </div>\n\n" ); + } + + list = dlist_next( list ); + } + files = tree_lines_free( files ); + } + + strbuf_addf( sb, " </div> <!-- End of Tree -->\n\n" ); + + free( path ); +} + +static void csvn_print_tree( struct strbuf *sb, const char *relative_path, int revision ) +{ + const char *co_prefix = ctx.repo.checkout_ro_prefix; + const char *repo_path = ctx.repo.name; + char *path = NULL; + + if( !sb ) return; + + if( relative_path && *relative_path ) + { + path = (char *)xmalloc( strlen( relative_path ) + 2 ); + path[0] = '/'; + sprintf( (char *)&path[1], relative_path ); + } + else + { + path = (char *)xmalloc( 1 ); + path[0] = '\0'; + } + + if( co_prefix ) + { + char cmd[1024]; + struct strbuf buf = STRBUF_INIT; + pid_t p = (pid_t) -1; + int rc; + + if( revision ) + snprintf( (char *)&cmd[0], 1024, + "svn list --revision %d --xml %s/%s%s 2>/dev/null", + revision, co_prefix, repo_path, path ); + else + snprintf( (char *)&cmd[0], 1024, + "svn list --xml %s/%s%s 2>/dev/null", + co_prefix, repo_path, path ); + p = sys_exec_command( &buf, cmd ); + rc = sys_wait_command( p, NULL ); + if( rc != 0 ) + { + strbuf_release( &buf ); + free( path ); + return; + } + + xml_csvn_tree( (const struct strbuf *)&buf ); + strbuf_release( &buf ); + + dlist_csvn_tree( sb, relative_path, revision ); + } + + free( path ); + return; +} + +static void csvn_print_checkout_urls( struct strbuf *sb, const char *relative_path, int revision ) +{ + char *path = NULL; + + if( !sb ) return; + + if( relative_path && *relative_path ) + { + path = (char *)xmalloc( strlen( relative_path ) + 2 ); + path[0] = '/'; + sprintf( (char *)&path[1], relative_path ); + } + else + { + path = (char *)xmalloc( 1 ); + path[0] = '\0'; + } + + strbuf_addf( sb, " <div class=\"checkout-box\">\n" ); + strbuf_addf( sb, " <div class=\"checkout-header\">Checkout</div>\n" ); + strbuf_addf( sb, " <div class=\"checkout-urls\">\n" ); + strbuf_addf( sb, " <div class=\"checkout-line\">\n" ); + strbuf_addf( sb, " <div class=\"checkout-perms\">ro:</div>\n" ); + + if( revision ) + strbuf_addf( sb, " <div class=\"checkout-url\">svn checkout --revision %d %s/%s%s</div>\n", revision, ctx.repo.checkout_ro_prefix, ctx.repo.name, path ); + else + strbuf_addf( sb, " <div class=\"checkout-url\">svn checkout %s/%s%s</div>\n", ctx.repo.checkout_ro_prefix, ctx.repo.name, path ); + + strbuf_addf( sb, " </div>\n" ); + if( ctx.repo.checkout_prefix && *ctx.repo.checkout_prefix ) + { + strbuf_addf( sb, " <div class=\"checkout-line\">\n" ); + strbuf_addf( sb, " <div class=\"checkout-perms\">rw:</div>\n" ); + + if( revision ) + strbuf_addf( sb, " <div class=\"checkout-url\">svn checkout --revision %d %s/%s%s</div>\n", revision, ctx.repo.checkout_prefix, ctx.repo.name, path ); + else + strbuf_addf( sb, " <div class=\"checkout-url\">svn checkout %s/%s%s</div>\n", ctx.repo.checkout_prefix, ctx.repo.name, path ); + + strbuf_addf( sb, " </div>\n" ); + } + strbuf_addf( sb, " </div>\n" ); + strbuf_addf( sb, " </div>\n" ); + + free( path ); +} + +void csvn_print_tree_page( void ) +{ + FILE *fp; + struct strbuf buf = STRBUF_INIT; + + fp = xfopen( ctx.page.header, "r" ); + (void)strbuf_env_fread( &buf, fp ); + fclose( fp ); + + strbuf_addf( &buf, " <div class=\"content segment\">\n" ); + strbuf_addf( &buf, " <div class=\"container\">\n" ); + strbuf_addf( &buf, " <div class=\"csvn-main-content\">\n" ); + + if( ctx.repo.relative_info.kind == KIND_DIR ) + { + if( ctx.repo.name ) + { + csvn_print_tree( &buf, ctx.repo.relative_path, ctx.query.rev ); + csvn_print_checkout_urls( &buf, ctx.repo.relative_path, ctx.query.rev ); + } + else + { + strbuf_addf( &buf, " <h1>Requested resource cannot be shown</h1>\n" ); + strbuf_addf( &buf, " <p class='leading'>Repository '%s' not found.</p>\n", ctx.repo.name ); + } + } + else + { + strbuf_addf( &buf, " <h1>Requested resource cannot be shown</h1>\n" ); + strbuf_addf( &buf, " <p class='leading'>This page assume the repository tree to be shown.</p>\n" ); + } + + strbuf_addf( &buf, " </div> <!-- End of csvn-main-content -->\n" ); + strbuf_addf( &buf, " </div> <!-- End of container -->\n" ); + strbuf_addf( &buf, " </div> <!-- End of content segment -->\n" ); + + fp = xfopen( ctx.page.footer, "r" ); + (void)strbuf_env_fread( &buf, fp ); + fclose( fp ); + + ctx.page.size = buf.len; + csvn_print_http_headers(); + strbuf_write( &buf, STDOUT_FILENO ); + strbuf_release( &buf ); +} |