#include #include #include #include /* for tolower() */ #include /* for stat() */ #include "global.h" #include "custom.h" /* for date FORMAT strings */ #include "manhat-lib/shared_util.h" #include "manhat-lib/shared_cs_util.h" #include "manhat-lib/shared_news_util.h" #include "manhat-lib/shared_lock.h" #include "manhat-lib/shared_server_string.h" #include "manhat-lib/shared_mime_type.h" #include "manhat-lib/shared_podcast_util.h" #include "manhat-lib/shared_access.h" ATTACHMENT_NODE *attachment_head = (ATTACHMENT_NODE *) 0; static void read_parameters (char *course, char *podcast_key) { cs_get_required_parameter ("crs", course, MAX_FILENAME); cs_get_required_parameter ("id", podcast_key, MAX_KEY); } static void memory_error () { cs_critical_error (ERR_MALLOC_FAILED, ""); }; static void free_attachment_list() { ATTACHMENT_NODE *ptr; while(attachment_head) { ptr = attachment_head; attachment_head = attachment_head -> next; free(ptr); } } static void build_attachment_list (MSG_TREE_NODE * msg, CONFIG_STRUCT *conf) { FILE *fp; int fd; char infopath[MAX_PATH + 1]; int i; ATTACHMENT_NODE *aptr = 0; NEWS_INFO info; int n_attachments; free_attachment_list(); if(!msg->data.info.attachments) return; n_attachments = abs(msg->data.info.attachments); /* since a -1 is used to flag an attached website */ snprintf (infopath, MAX_PATH + 1, "%s%s/%s/%s/%s", conf->course_path, PEOPLE_DIR, msg->data.info.sender, discussion_fname, msg->data.info_fname); fp = fopen (infopath, "r"); if (!fp) cs_critical_error (ERR_FOPEN_READ_FAILED, infopath); fd = fileno(fp); get_shared_lock(fd, 1); /* shared_lock.c */ if (fread (&info, sizeof (NEWS_INFO), 1, fp) != 1) cs_critical_error (ERR_FREAD_FAILED, infopath); /* process attachments, build linked list */ for (i = 1; i <= n_attachments; i++) { if (!attachment_head) { attachment_head = (ATTACHMENT_NODE *) malloc (sizeof (ATTACHMENT_NODE)); if (!attachment_head) memory_error (); aptr = attachment_head; } else { aptr->next = (ATTACHMENT_NODE *) malloc (sizeof (ATTACHMENT_NODE)); if (!aptr->next) memory_error (); aptr = aptr->next; } if (fread (&aptr->data, sizeof (ATTACHMENT), 1, fp) != 1) cs_critical_error (ERR_FREAD_FAILED, infopath); aptr->next = 0; } /* for .. */ release_lock(fd); /* shared_lock.c */ fclose(fp); } static void get_attachment_size(CONFIG_STRUCT *conf, const char *sender, const char *unique_fname, long *file_size) { char path[MAX_PATH + 1]; struct stat file_stat; snprintf (path, MAX_PATH + 1, "%s%s/%s/%s/%s", conf->course_path, PEOPLE_DIR, sender, discussion_fname, unique_fname); if(stat(path, &file_stat)) *file_size = 0; else *file_size = file_stat.st_size; } static void send_xml_escaped_string(char *string) { char *ptr; if(string) { for(ptr=string; *ptr; ptr++) switch(*ptr) { case '<': fputs("<", stdout); break; case '>': fputs(">", stdout); break; case '&': fputs("&", stdout); break; case '"': fputs(""", stdout); break; case '\'': fputs("'", stdout); break; default: fputc(*ptr, stdout); break; } } } static void send_description(const char *file) { struct stat file_stat; FILE *fp; char *buf = 0; fp = fopen (file, "r"); if (!fp) cs_critical_error (ERR_FOPEN_READ_FAILED, file); /* shared_cs_util.c */ if(fstat(fileno(fp),&file_stat)) cs_critical_error(ERR_STAT_FAILED, file); /* shared_cs_util.c */ if(file_stat.st_size > 0) { buf = (char*) malloc( sizeof(char)*(file_stat.st_size +1)); if(!buf) cs_critical_error(ERR_MALLOC_FAILED, file); /* shared_cs_util.c */ if(fread(buf, file_stat.st_size, 1, fp) != 1) cs_critical_error(ERR_FREAD_FAILED, file); /* shared_cs_util.c */ fclose(fp); buf[file_stat.st_size]='\0'; } else { buf = (char*) malloc( sizeof(char)*(2)); *buf = '\0'; } puts(""); send_xml_escaped_string(buf); puts(""); if(buf) free(buf); } void send_one_podcast(MSG_TREE_NODE *msg_ptr, CONFIG_STRUCT *conf, const char *course, const char *server_string, const char *podcast_key) { char msg_path[MAX_PATH +1]; char extension[MAX_FILENAME +1]; char timestring[MAX_TIMESTRING +1]; char mime_type[MAX_MIME_TYPE + 1]; long attachment_size; build_attachment_list (msg_ptr, conf); puts (""); puts(""); send_xml_escaped_string(msg_ptr->data.info.subject); puts(""); if(attachment_head) { get_extension(attachment_head->data.orig_fname, extension); /* shared_news_util.c */ get_mime_type_from_extension(extension, mime_type); /* shared_mime_type.c */ get_attachment_size(conf,msg_ptr->data.info.sender, attachment_head->data.unique_fname, &attachment_size); /** Following URL will not work in iTunes and some other podcast aggregators *** if & is escaped as & !!!! */ printf("\n", server_string, ALIAS, extension, course, podcast_key, attachment_head->data.unique_fname, msg_ptr->data.info.sender, msg_ptr->data.info_fname, extension, attachment_size, mime_type); } snprintf (msg_path, MAX_PATH + 1, "%s%s/%s/%s/%s", conf->course_path, PEOPLE_DIR, msg_ptr->data.info.sender, discussion_fname, msg_ptr->data.info.msg_fname); send_description(msg_path); /* guid is an optional sub-element of ** ** guid stands for globally unique identifier. It's a string that uniquely identifies the item. ** When present, an aggregator may choose to use this string to determin if an item is new. ** ** There are no rules for the syntax of a guid. Aggregators mus view them as a string. It's up ** to the source of the feed to establish the uniqueness of the string. ** ** If the guid element has an attribute "isPermaLink" with a value of true, the reader may assume ** that it is a permalink to the item, that is, a url that can be opened in a web browser, that ** points to the full item described by the element. ** ** isPermaLink is optional, its default value is true. If its value is false, the guid may not be ** assumed to be a url, or a url to anything in particular. ** ** In our case, we can't have a URL to the , since the user would have to login. Set isPermaLink ** to false, and set the string to the course and offset of the message. That should be sufficient ** for an aggregator to figure out if the item is new */ printf("%s %ld\n", course, msg_ptr->offset); /* Must use RFC 822 format for this date - #defined in custom.h */ strftime (timestring, MAX_TIMESTRING, RFC822_DATE_FORMAT,localtime (&msg_ptr->data.info.time_sent)); printf("%s\n", timestring); puts(""); } void send_channel_data(CONFIG_STRUCT *conf, const char *course, const char *server_string) { puts (""); /* REQUIRED * title - The name of your channel. It's how people refer to your service. * If you have an HTML website that contains the same information as your RSS * file, the title of your channel should be the same as the title of your * website. */ printf("" ); send_xml_escaped_string(conf->course_no); putc(' ', stdout); // send_xml_escaped_string(conf->title); // putc(' ', stdout); send_xml_escaped_string(conf->semester); putc(' ', stdout); send_xml_escaped_string(conf->instructor); puts(""); /* REQUIRED * link - The URL to the HTML website corresponding to the channel. * * If this is a standalone course, provide a direct login link to the course, * otherwise let's use the EXIT_URL as #defined in custom.h (hopefully this * will have a login form and/or instructions */ if(conf->standalone) printf("%s/%s/doorstep?class=%s\n", server_string, ALIAS, course); else { printf(""); send_xml_escaped_string(EXIT_URL); /* #defined in custom.h */ puts(""); } /* REQUIRED * description - Phrase or sentence describing the channel */ puts(""); send_xml_escaped_string(conf->course_no); putc(' ', stdout); send_xml_escaped_string(conf->semester); putc(' ', stdout); send_xml_escaped_string(conf->title); putc(' ', stdout); send_xml_escaped_string(conf->instructor); puts(""); /* OPTIONAL ** generator - A string indicating the program used to generate the channel **/ printf("The Manhattan Virtual Classroom - "); send_xml_escaped_string(VERSION); /* defined in global.h */ puts(""); } static void send_podcast_rss_data (CONFIG_STRUCT *conf, SESSION *user, const char *course, const char *podcast_key) { MSG_TREE_NODE *msg_ptr; time_t current_time; char server_string[MAX_PATH + 1]; /* current_time is declared globally */ build_msg_tree(user, PODCASTS, 0, 0, ¤t_time); /* shared_news_util.c */ puts ("Content-type:text/xml\n"); puts (""); puts (""); get_server_string(server_string, MAX_PATH +1); send_channel_data(conf, course, server_string); for(msg_ptr=tree_head; msg_ptr; msg_ptr=msg_ptr->next) send_one_podcast(msg_ptr, conf, course, server_string, podcast_key); puts(""); puts(""); free_msg_tree (tree_head); /* shared_news_util.c */ } /** This program does no authentication, yet we need a SESSION user *** This function sets up a 'dummy' user as a student. **/ static void get_dummy_student_user(SESSION *user) { strncpy(user->username, "_unknown_", MAX_USERNAME + 1 ); strncpy(user->realname, "_unknown_", MAX_NAME + 1); strncpy(user->id,"_unknown_id_", MAX_ID + 1); strncpy(user->alias, "_unknown_", MAX_NAME + 1); user->team = '!'; user->group = STUDENT; user->expires = 0; } int main (void) { SESSION user; CONFIG_STRUCT conf; char podcast_key[MAX_KEY + 1]; /* from id=? command line */ char course[MAX_PATH + 1]; /* from crs=? command line */ cs_cgi_init(); read_parameters (course, podcast_key); read_configuration_file (course, &conf); /* shared_util.c */ validate_podcast_key(podcast_key, &conf); /* shared_podcast_util.c */ /* since we're not really authenticating, fill in some dummy values for ** a student that can safely be used throughout this program */ get_dummy_student_user(&user); has_write_permission(&user, &conf); /* shared_access.c - access is denied if user can't view this course */ set_discussion_names (conf.course_path, user.username, PODCASTS); /* shared_news_util.c */ send_podcast_rss_data (&conf, &user, course, podcast_key); cs_cgi_destroy(); return 0; }