/* * Copyright 1995, 1998 Perforce Software. * * This file is part of WebKeeper, a perforce client apache module. * * License is hereby granted to use this software and distribute it * freely, as long as this copyright notice is retained and modifications * are clearly marked. * * ALL WARRANTIES ARE HEREBY DISCLAIMED. * * $Id: //public/perforce/webkeeper/mod_webkeep.c#7 $ */ /* * mod_webkeep.c: the apache side glue (in C) to the Perforce client * * This module by Perforce Software, from a template by the Apache Group. */ /* * TODO: Is there a good way to support mass virtual hosting? */ #include "httpd.h" #include "http_config.h" #include "mod_webkeep.h" int webKeepCallP4( void (*func)(), request_rec *r, WebKeepConnect *p4, const char *arg ); typedef struct { char *real; char *fake; } alias_entry; typedef struct { array_header *aliases; array_header *index_names; array_header *refresh_list; WebKeepConnect p4; } alias_server_conf; typedef struct { char *filename; int revision; char *filetype; } file_dir_info; module webkeep_module; void init_webkeep( server_rec *s, pool *p ) { ap_add_version_component( "WebKeeper/0.7" ); } static void *create_webkeep_config (pool *p, server_rec *s) { alias_server_conf *a; a = (alias_server_conf *)ap_pcalloc (p, sizeof(alias_server_conf)); a->aliases = ap_make_array (p, 20, sizeof(alias_entry)); a->index_names = NULL; a->refresh_list = NULL; a->p4.port = 0; a->p4.user = 0; a->p4.pass = 0; a->p4.client = 0; a->p4.sync = 0; return a; } static void *merge_webkeep_config (pool *p, void *basev, void *overridesv) { alias_server_conf *a, *b, *o; a = (alias_server_conf *)ap_pcalloc (p, sizeof(alias_server_conf)); b = (alias_server_conf *)basev; o = (alias_server_conf *)overridesv; a->aliases = ap_append_arrays (p, o->aliases, b->aliases); a->index_names = o->index_names ? o->index_names : b->index_names; a->refresh_list = o->refresh_list ? o->refresh_list : b->refresh_list; a->p4.port = o->p4.port ? o->p4.port : a->p4.port ? a->p4.port : b->p4.port; a->p4.user = o->p4.user ? o->p4.user : a->p4.user ? a->p4.user : b->p4.user; a->p4.pass = o->p4.pass ? o->p4.pass : a->p4.pass ? a->p4.pass : b->p4.pass; a->p4.client = o->p4.client ? o->p4.client : a->p4.client ? a->p4.client : b->p4.client; a->p4.sync = o->p4.sync ? o->p4.sync : a->p4.sync ? a->p4.sync : b->p4.sync; return a; } static const char *add_webkeep(cmd_parms *cmd, void *dummy, char *f, char *r) { server_rec *s = cmd->server; alias_server_conf *conf; alias_entry *new; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); new = ap_push_array (conf->aliases); /* XX r can NOT be relative to DocumentRoot here... compat bug. */ new->fake = f; new->real = r; return NULL; } static const char *port_webkeep(cmd_parms *cmd, void *d, char *f) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); conf->p4.port = f; return NULL; } static const char *user_webkeep(cmd_parms *cmd, void *d, char *f) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); conf->p4.user = f; return NULL; } static const char *passwd_webkeep(cmd_parms *cmd, void *d, char *f) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); conf->p4.pass = f; return NULL; } static const char *client_webkeep(cmd_parms *cmd, void *d, char *f) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); conf->p4.client = f; return NULL; } static const char *sync_webkeep(cmd_parms *cmd, void *d, int f) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); conf->p4.sync = f; return NULL; } static const char *index_webkeep( cmd_parms *cmd, void *dummy, char *arg ) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); if ( !conf->index_names ) conf->index_names = ap_make_array( cmd->pool, 2, sizeof( char * ) ); *(char **)ap_push_array( conf->index_names ) = arg; return NULL; } static const char *refresh_webkeep( cmd_parms *cmd, void *dummy, char *arg ) { server_rec *s = cmd->server; alias_server_conf *conf; conf = (alias_server_conf *) ap_get_module_config(s->module_config,&webkeep_module); if ( !conf->refresh_list ) conf->refresh_list = ap_make_array( cmd->pool, 2, sizeof( char * ) ); *(char **)ap_push_array( conf->refresh_list ) = arg; return NULL; } static command_rec alias_cmds[] = { { "WebKeepAlias", add_webkeep, NULL, RSRC_CONF, TAKE2, "a fakename and a depot name"}, { "WebKeepPort", port_webkeep, NULL, RSRC_CONF, TAKE1, "a TCP/IP host:port"}, { "WebKeepUser", user_webkeep, NULL, RSRC_CONF, TAKE1, "a Perforce user name"}, { "WebKeepPasswd", passwd_webkeep, NULL, RSRC_CONF, TAKE1, "a Perforce user password"}, { "WebKeepClient", client_webkeep, NULL, RSRC_CONF, TAKE1, "a Perforce client name"}, { "WebKeepSync", sync_webkeep, NULL, RSRC_CONF, FLAG, "a Perforce client name"}, { "WebKeepDirectoryIndex", index_webkeep, NULL, OR_INDEXES, ITERATE, "a list of file names"}, { "WebKeepRefresh", refresh_webkeep, NULL, RSRC_CONF, ITERATE, "a list of Perforce file specs"}, { NULL } }; static int alias_matches (char *uri, char *alias_fakename) { char *end_fakename = alias_fakename + strlen (alias_fakename); char *aliasp = alias_fakename, *urip = uri; while (aliasp < end_fakename) { if (*aliasp == '/') { /* any number of '/' in the alias matches any number in * the supplied URI, but there must be at least one... */ if (*urip != '/') return 0; while (*aliasp == '/') ++ aliasp; while (*urip == '/') ++ urip; } else if ( aliasp == end_fakename - 1 && *aliasp == '$') { /* a $ at the end of the fakename means it must match * the IRI exactly: i.e. no initial substring match. */ if (*urip != 0) return 0; ++ aliasp; } else { /* Other characters are compared literally */ if (*urip++ != *aliasp++) return 0; } } /* Check last alias path component matched all the way */ if (aliasp[-1] != '/' && *urip != '\0' && *urip != '/') return 0; /* Return number of characters from URI which matched (may be * greater than length of alias, since we may have matched * doubled slashes) */ return urip - uri; } static void refresh_sync_webkeep( request_rec *r, alias_server_conf *conf ) { char **names_ptr; int num_names; if( conf->refresh_list == NULL || conf->p4.client == NULL ) { return; } names_ptr = (char **) conf->refresh_list->elts; num_names = conf->refresh_list->nelts; for( ; num_names; ++names_ptr, --num_names ) { char *name_ptr = *names_ptr; webKeepCallP4( webKeepSync, r, &conf->p4, name_ptr ); } } static int post_read_req_webkeep(request_rec *r) { int i; void *sconf; alias_server_conf *conf; alias_entry *entries; int isFile, isDir = 0; if (r->uri[0] != '/' && r->uri[0] != '\0') return BAD_REQUEST; sconf = r->server->module_config; conf = (alias_server_conf *)ap_get_module_config(sconf, &webkeep_module); entries = (alias_entry *)conf->aliases->elts; refresh_sync_webkeep( r, conf ); for (i = 0; i < conf->aliases->nelts; ++i) { alias_entry *p = &entries[i]; int l = alias_matches (r->uri, p->fake); debugS("post_read_req: alias_matches b/w %s and %s returned %d\n", r->uri, p->fake, l); if (l > 0) { char *depotPath; char *dirPath; /* * We stash this under webkeep-path, instead of r->filename, * because (a) we have to communicate to our handle_webkeep * that this is one of ours and (b) because filename gets * mangled by the access checking code. There must be a * way around this, but it is buried in the minds of the * apache writers. */ depotPath = ap_pstrcat(r->pool, p->real, r->uri + l, NULL); /* * If this is a directory, invoke the DirectoryIndex mechanism. * Although the code is lifted from mod_dir, we don't deal with * this in the content handler, because we can't be guaranteed * that the directory exists in the local file system for the * type checking to have recognized it as a directory. */ if( webKeepDirExists( &conf->p4, depotPath ) ) { char **names_ptr; int num_names; char *dummy_ptr[1]; isFile = 0; isDir = 1; /* * Blatantly stolen from mod_dir.c */ /* * Since I know this is a directory, make sure it ends in * a slash. If it doesn't issue a redirect. */ if (r->uri[0] == '\0' || r->uri[strlen(r->uri) - 1] != '/') { char *ifile; if (r->args != NULL) ifile = ap_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), "/", "?", r->args, NULL); else ifile = ap_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), "/", NULL); ap_table_setn(r->headers_out, "Location", (char *) ap_construct_url(r->pool, ifile, r)); debugS("post_read_req: Is dir, no slash. Issue redirect to %s\n", ifile, "", ""); return HTTP_MOVED_PERMANENTLY; } if( conf->index_names ) { names_ptr = (char **)conf->index_names->elts; num_names = conf->index_names->nelts; } else { dummy_ptr[0] = DEFAULT_INDEX; names_ptr = dummy_ptr; num_names = 1; } for( ; num_names; ++names_ptr, --num_names ) { char *name_ptr = *names_ptr; char *testPath; if( depotPath[strlen( depotPath ) - 1] == '/' ) { testPath = ap_pstrcat( r->pool, depotPath, name_ptr, NULL ); } else { testPath = ap_pstrcat( r->pool, depotPath, "/", name_ptr, NULL ); } if( webKeepFileExists( &conf->p4, testPath ) ) { depotPath = testPath; isFile = 1; isDir = 0; break; } } } ap_table_set( r->notes, "webkeep-path", depotPath ); if( conf->p4.sync && webKeepFileExists( &conf->p4, depotPath ) ) { debug("post_read_req: Either sync is set or file exists. Returning DECLINED\n"); webKeepCallP4( webKeepSync, r, &conf->p4, depotPath ); return DECLINED; } /* To save processing time and to reduce the impact of webkeeper requests * on the p4d server, cache results of any 'dirs' and 'files' requests * that you make */ if (isFile == 1) { ap_table_set( r->notes, "webkeep-path-type", "file" ); } else if(isDir == 1) { ap_table_set( r->notes, "webkeep-path-type", "dir" ); } debugS("post_read_req: Returning OK for depotPath: %s\n", depotPath, "", ""); return OK; } } debug("post_read_req: No aliases->nelts?. Returning DECLINED\n"); return DECLINED; } void parsefileinfo(request_rec *r, const char *data, char **fname, char **rev, char **action, char **chgnum, char **filetype) { const char *q; /* sigh... */ char *p; char *buf; /* filename follows last '/'. Allow for the (possible?) case that * there is no path */ q = strrchr(data, '/'); q = q ? q + 1 : data; /* allocate buffer to hold a copy of the entire data string, from * the beginning of the filename and onwards. the pointer-pointers * passed as parameters are set to point into the copy of the * string, with ascii-nulls inserted to delimit parameters in the * string. */ buf = ap_pstrcat(r->pool, q, 0); ap_assert(buf != 0); /* Ok - filename up until '#' ... */ *fname = buf; p = strchr(*fname, '#'); ap_assert(p != 0); *p = 0; /* Then file revision up until the first blank (space) */ *rev = p + 1; p = strchr(*rev, ' '); ap_assert(p != 0); *p = 0; /* skip following "- "... */ ap_assert(*++p == '-'); ap_assert(*++p == ' '); /* action up until next blank */ *action = p + 1; p = strchr(*action, ' '); ap_assert(p != 0); *p = 0; /* filetype in brackets - find opening bracket */ p = strchr(p + 1, '('); ap_assert(p != 0); *filetype = p + 1; p = strchr(*filetype, ')'); ap_assert(p != 0); *p = 0; } static void appendfile(WebKeepPrinter *printer, const char *data) { file_dir_info *new; char *fname, *rev, *action, *chgnum, *ftype; request_rec *r = (request_rec *)printer->closure; parsefileinfo(r, data, &fname, &rev, &action, &chgnum, &ftype); if (strcmp(action, "delete") == 0) { return; } /* else */ if (printer->files == 0) { printer->files = ap_make_array(r->pool, 10, sizeof(file_dir_info)); ap_assert(printer->files != 0); } new = (file_dir_info *) ap_push_array((array_header *) printer->files); ap_assert(new != 0); new->filename = fname; new->revision = atoi(rev); new->filetype = ftype; debugS("appendDir : Adding File %s of type %s and revision %s to files list\n", fname, rev, ftype); } static void appenddir(WebKeepPrinter *printer, const char *data) { char **new; char *p; const char *q; request_rec *r = (request_rec *)printer->closure; if (printer->dirs == 0) { printer->dirs = ap_make_array(r->pool, 10, sizeof(char *)); ap_assert(printer->dirs != 0); } new = (char **) ap_push_array(printer->dirs); ap_assert(new != 0); /* find last path separator, if any */ /* assume that there is no trailing path separator */ q = strrchr(data, '/'); q = q ? q + 1 : data; debugS("appendDir : Adding dir %s to dirs list.Original data: %s\n", q, data, ""); *new = ap_pstrcat(r->pool, q, 0); ap_assert(*new != 0); } /* Webkeeper glue */ /* Protocol: * * webKeepData() is called every time something is sent through the * OutputInfo() method. webKeepData() examines the WebKeepPrinter * structure, and does one of the following: * 1) If we're currently creating a "directory listing": analyse the * information we were passed, and append file or directory data to * the WebKeepPrinter structure. * 2) If we're printing a file: analyse the information passed, and * set the content-type element in the WebKeepPrinter structure * accordingly. * * webKeepError() is called whenever something is sent through the * OutputError() method. webKeepError() examines the WebKeepPrinter * structure, increments the error count in the WebKeepPrinter structure * and logs an error (unless a flag in the WebKeepPrinter structure * tells it to be quiet). * * webKeepText() is called to output data from the Perforce * API. * * webKeepDir() is called to dump out a directory structure. * */ /* * Expected protocol: * * Success: * data block with filename/type * one or more text blocks * * Error: * error block */ void webKeepData( WebKeepPrinter *printer, const char *data) { request_rec *r = (request_rec *)printer->closure; debugS("webKeepData: Interpreting data: %s collectinfo: %d ; print: %d \n", data, printer->collectinfo, printer->print); if (printer->collectinfo) { /* directory building */ if (strchr(data, '#') != 0) { /* file element */ appendfile(printer, data); } else { appenddir(printer, data); } } else if (printer->print) { /* printing file */ if (!r->content_type && strlen(data) > 5 && strcmp(data + strlen(data) - 5, "text)") != 0) { if (strstr(data, ".gif") != NULL) /* GIF */ r->content_type = "image/gif"; else if (strstr(data, ".jpg") != NULL) /* jpg */ r->content_type = "image/jpeg"; else /* binary */ r->content_type = "application/octet-stream"; debugS("webKeepData: Determined non-text data of type: %s\n", r->content_type, "", ""); } /* Now that we've seen the header, we clear the status */ printer->status = DONE; ap_send_http_header( r ); } } void webKeepError( WebKeepPrinter *printer, char *buf , int status) { printer->errorcount++; debugS("WebKeepError : Reporting error with status %d", status, "", ""); if (! printer->ignoreerrors) { request_rec *r = (request_rec *)printer->closure; ap_log_reason(buf, r->uri, r); switch (status) { case __WK_BAD_REQUEST : printer->status = BAD_REQUEST; break; case __WK_SERVER_ERROR : printer->status = SERVER_ERROR; break; case __WK_NOT_FOUND : printer->status = NOT_FOUND; break; default : fprintf(stderr, "Couldn't match error code from WebKeepError to a known code.\n"); printer->status = status; break; } } } void webKeepText( WebKeepPrinter *printer, char *buf, int len ) { request_rec *r = (request_rec *)printer->closure; debugS("WebKeepText : Printing data of len %d . printer status: %d ; DONE is %d\n", len, printer->status, DONE); printer->status = DONE; while( len-- ) ap_rputc( *buf++, r ); } const char *iconbytype(const char *type) { static const char* lookup[] = { "dir", "folder.gif", /* non-perforce file type */ "text", "text.gif", "ktext", "text.gif", "binary", "binary.gif", 0, 0 }; const char **p; for (p = lookup; *p; p += 2) { if (strcmp(*p, type) == 0) { return *(p + 1); } } /* else */ return "unknown.gif"; } void embedimagebytype(request_rec *r, const char *type) { ap_rprintf(r, "%s", "<td><img src=\"/icons/"); ap_rprintf(r, "%s", iconbytype(type)); ap_rprintf(r, "%s", "\"></td>"); } void webKeepDir(WebKeepPrinter *printer) { request_rec *r = printer->closure; int i; char **cpp; file_dir_info *fdip; r->content_type = "text/html"; printer->status = DONE; ap_send_http_header(r); ap_rprintf(r, "<html><head>\n"); ap_rprintf(r, "<title>Directory Output</title>\n"); ap_rprintf(r, "</head>\n"); ap_rprintf(r, "<body>\n"); ap_rprintf(r, "<h2>Contents of %s:</h2><br>\n", r->uri); ap_rprintf(r, "<table>\n"); ap_rprintf(r, "<tr>"); embedimagebytype(r, "dir"); ap_rprintf(r, "<td><a href=\"../\">Parent directory</a></td></tr>\n"); if (printer->dirs != 0) { ap_rprintf(r, "<tr><td> </td></tr>\n"); for (i = 0, cpp = (char **) ((array_header *) printer->dirs)->elts; i < ((array_header *) printer->dirs)->nelts; cpp++, i++) { ap_rprintf(r, "<tr>"); embedimagebytype(r, "dir"); ap_rprintf(r, "<td><a href=\"%s/\">%s</a><br></td></tr>\n", *cpp, *cpp); } } if (printer->files != 0) { ap_rprintf(r, "<tr><td> </td></tr>\n"); for (i = 0, fdip = (file_dir_info *) ((array_header *) printer->files)->elts; i < ((array_header *) printer->files)->nelts; i++, fdip++) { ap_rprintf(r, "<tr>"); embedimagebytype(r, fdip->filetype); ap_rprintf(r, "<td><a href=\"%s\">%s#%d</a></td><td>(%s)</td></tr>\n", fdip->filename, fdip->filename, fdip->revision, fdip->filetype); } } ap_rprintf(r, "</table>"); ap_rprintf(r, "</body></html>\n"); } int webKeepCallP4( void (*func)(), request_rec *r, WebKeepConnect *p4, const char *arg ) { const char *pathType; WebKeepPrinter printer; printer.status = NOT_FOUND; printer.closure = (void *)r; printer.data = webKeepData; printer.text = webKeepText; printer.error = webKeepError; printer.dir = webKeepDir; printer.dirs = printer.files = 0; printer.isFile = printer.isDir = -1; pathType = ap_table_get(r->notes, "webkeep-path-isFile"); if (pathType) { if (strcmp(pathType, "file")) { printer.isFile = 1; printer.isDir = 0; } else if(strcmp(pathType, "dir")) { printer.isDir = 1; printer.isFile = 0; } else { fprintf(stderr, "webKeepCallP4: Can't determine pathType %s. Returning error.\n", pathType); printer.status = SERVER_ERROR; return SERVER_ERROR; } } (*func)( p4, arg, &printer ); debugS("webKeepCallP4: function set printer status to %d\n", printer.status, "", ""); return printer.status; } /* The formal handler... */ static int handle_webkeep (request_rec *r) { void *sconf; alias_server_conf *conf; const char *depotPath; int status; /* For us? */ debug("handle_webkeep: Starting handling of the request\n"); if (r->method_number != M_GET) return DECLINED; depotPath = ap_table_get(r->notes, "webkeep-path"); if (! depotPath ) return DECLINED; /* The mulberry bush. */ sconf = r->server->module_config; conf = (alias_server_conf *)ap_get_module_config(sconf, &webkeep_module); debug("handle_webkeep: Checking if sync is set\n"); if( conf->p4.sync ) return DECLINED; debug("handle_webkeep: Calling webKeepPrint\n"); status = webKeepCallP4( webKeepPrint, r, &conf->p4, depotPath ); debugS("handle_webkeep: Returning status %s ; status code: %d\n", (status == OK) ? "OK" : (status == DECLINED) ? "DECLINED" : (status == DONE) ? "DONE" : (status == HTTP_NOT_FOUND) ? "NOT_FOUND" : (status == SERVER_ERROR) ? "SERVER_ERROR" : (status == BAD_REQUEST) ? "BAD_REQUEST" : "Unmapped status. Check against httpd.h" , status, ""); return status; } handler_rec webkeep_handlers[] = { { "*/*", handle_webkeep }, { NULL } }; module webkeep_module = { STANDARD_MODULE_STUFF, init_webkeep, /* initializer */ NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ create_webkeep_config, /* server config */ merge_webkeep_config, /* merge server configs */ alias_cmds, /* command table */ webkeep_handlers, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* process initialization */ NULL, /* process exit/cleanup */ post_read_req_webkeep /* post read_request handling */ };
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 1844 | Chetan Patil |
Made following changes to the webkeeper code: - Directory listing (uses p4 dirs and files to build the listing) - If for a directory, the trailing slash is missing, redirect to a new url with a trailing slash (mod_dir code). - Debug statements for debugging - Bug fix where sync was being used unintialized (mod_webkeep2.cc). This has not been tested with the syncing capability of Webkeeper. - Code outputs http content type based on if the data is gif, jpeg or binary data; previously it only output octet-stream for all non-textual data. - Code tries to reduce the impact on the perforce server by caching some information between processing phases (isFile and isDir) - Misc Minor edits |