mirror of
				https://github.com/ElvishArtisan/rivendell.git
				synced 2025-10-31 14:13:52 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			380 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			380 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // rdfeed_script.cpp
 | |
| //
 | |
| // An RSS Feed Generator for Rivendell.
 | |
| //
 | |
| //   (C) Copyright 2002-2007,2016-2018 Fred Gleason <fredg@paravelsystems.com>
 | |
| //
 | |
| //   This program is free software; you can redistribute it and/or modify
 | |
| //   it under the terms of the GNU General Public License version 2 as
 | |
| //   published by the Free Software Foundation.
 | |
| //
 | |
| //   This program is distributed in the hope that it will be useful,
 | |
| //   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| //   GNU General Public License for more details.
 | |
| //
 | |
| //   You should have received a copy of the GNU General Public
 | |
| //   License along with this program; if not, write to the Free Software
 | |
| //   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | |
| //
 | |
| 
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <fcntl.h>
 | |
| #include <ctype.h>
 | |
| 
 | |
| #include <map>
 | |
| 
 | |
| #include <qapplication.h>
 | |
| #include <qdatetime.h>
 | |
| #include <qstringlist.h>
 | |
| 
 | |
| #include <rdapplication.h>
 | |
| #include <rdconf.h>
 | |
| #include <rddb.h>
 | |
| #include <rdescape_string.h>
 | |
| #include <rdfeed.h>
 | |
| #include <rdfeedlog.h>
 | |
| #include <rdformpost.h>
 | |
| #include <rdpodcast.h>
 | |
| #include <dbversion.h>
 | |
| #include <rdweb.h>
 | |
| 
 | |
| #include "rdfeed_script.h"
 | |
| 
 | |
| char server_name[PATH_MAX];
 | |
| 
 | |
| MainObject::MainObject(QObject *parent)
 | |
|   :QObject(parent)
 | |
| {
 | |
|   QString err_msg;
 | |
| 
 | |
|   char keyname[10];
 | |
|   int cast_id=-1;
 | |
|   bool count;
 | |
| 
 | |
|   //
 | |
|   // Validate Feed Key Name
 | |
|   //
 | |
|   if(getenv("QUERY_STRING")==NULL) {
 | |
|     printf("Content-type: text/html\n");
 | |
|     printf("Status: 400\n");
 | |
|     printf("\n");
 | |
|     printf("rdfeed: missing feed key name\n");
 | |
|     exit(0);
 | |
|   }
 | |
|   int arg=0;
 | |
|   while((getenv("QUERY_STRING")[arg]!=0)&&
 | |
| 	(getenv("QUERY_STRING")[arg]!='&')&&(arg<9)) {
 | |
|     keyname[arg]=getenv("QUERY_STRING")[arg];
 | |
|     arg++;
 | |
|   }
 | |
|   if(arg==9) {
 | |
|     printf("Content-type: text/html\n");
 | |
|     printf("Status: 400\n");
 | |
|     printf("\n");
 | |
|     printf("rdfeed: invalid feed key name\n");
 | |
|     exit(0);
 | |
|   }
 | |
|   keyname[arg]=0;
 | |
|   RDGetPostInt(getenv("QUERY_STRING")+arg+1,"cast_id",&cast_id);
 | |
| 
 | |
|   //
 | |
|   // Get the Server Name
 | |
|   //
 | |
|   if(getenv("SERVER_NAME")==NULL) {
 | |
|     printf("Content-type: text/html\n");
 | |
|     printf("Status: 500\n");
 | |
|     printf("\n");
 | |
|     printf("rdfeed: missing SERVER_NAME\n");
 | |
|     exit(0);
 | |
|   }
 | |
|   strncpy(server_name,getenv("SERVER_NAME"),PATH_MAX);
 | |
| 
 | |
|   //
 | |
|   // Determine Range
 | |
|   //
 | |
|   if(getenv("HTTP_RANGE")!=NULL) {
 | |
|     count=ShouldCount(getenv("HTTP_RANGE"));
 | |
|   }
 | |
|   else {
 | |
|     count=true;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open the Database
 | |
|   //
 | |
|   rda=new RDApplication("rdfeed.xml","rdfeed.xml",RDFEED_XML_USAGE,this);
 | |
|   if(!rda->open(&err_msg)) {
 | |
|     printf("Content-type: text/html\n");
 | |
|     printf("Status: 500\n");
 | |
|     printf("\n");
 | |
|     printf("rdfeed.xml: %s\n",(const char *)err_msg.utf8());
 | |
|     exit(0);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|   printf("Content-type: text/html\n\n");
 | |
|   QString sql;
 | |
|   RDSqlQuery *q;
 | |
|   sql=QString("show variables like '%character_set%'");
 | |
|   q=new RDSqlQuery(sql);
 | |
|   while(q->next()) {
 | |
|     printf("%s: %s<br>\n",(const char *)q->value(0).toString(),
 | |
| 	   (const char *)q->value(1).toString());
 | |
|   }
 | |
|   delete q;
 | |
|   sql=QString("show variables like '%collation%'");
 | |
|   q=new RDSqlQuery(sql);
 | |
|   while(q->next()) {
 | |
|     printf("%s: %s<br>\n",(const char *)q->value(0).toString(),
 | |
| 	   (const char *)q->value(1).toString());
 | |
|   }
 | |
|   delete q;
 | |
|   exit(0);
 | |
|   */
 | |
| 
 | |
|   if(cast_id<0) {
 | |
|     ServeRss(keyname,count);
 | |
|   }
 | |
|   ServeLink(keyname,cast_id,count);
 | |
| }
 | |
| 
 | |
| 
 | |
| void MainObject::ServeRss(const char *keyname,bool count)
 | |
| {
 | |
|   QString sql;
 | |
|   RDSqlQuery *q;
 | |
|   RDSqlQuery *q1;
 | |
| 
 | |
|   sql=QString("select ")+
 | |
|     "CHANNEL_TITLE,"+        // 00
 | |
|     "CHANNEL_DESCRIPTION,"+  // 01
 | |
|     "CHANNEL_CATEGORY,"+     // 02
 | |
|     "CHANNEL_LINK,"+         // 03
 | |
|     "CHANNEL_COPYRIGHT,"+    // 04
 | |
|     "CHANNEL_WEBMASTER,"+    // 05
 | |
|     "CHANNEL_LANGUAGE,"+     // 06
 | |
|     "LAST_BUILD_DATETIME,"+  // 07
 | |
|     "ORIGIN_DATETIME,"+      // 08
 | |
|     "HEADER_XML,"+           // 09
 | |
|     "CHANNEL_XML,"+          // 10
 | |
|     "ITEM_XML,"+             // 11
 | |
|     "BASE_URL,"+             // 12
 | |
|     "ID,"+                   // 13
 | |
|     "UPLOAD_EXTENSION,"+     // 14
 | |
|     "CAST_ORDER,"+           // 15
 | |
|     "REDIRECT_PATH,"+        // 16
 | |
|     "BASE_PREAMBLE "+        // 17
 | |
|     "from FEEDS where "+
 | |
|     "KEY_NAME=\""+RDEscapeString(keyname)+"\"";
 | |
|   q=new RDSqlQuery(sql);
 | |
|   if(!q->first()) {
 | |
|     printf("Content-type: text/html\n\n");
 | |
|     printf("rdfeed: no feed matches the supplied key name\n");
 | |
|     exit(0);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Log the Access
 | |
|   //
 | |
|   if(count) {
 | |
|     RDIncrementFeedCount(keyname);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Redirect if necessary
 | |
|   //
 | |
|   if(!q->value(16).toString().isEmpty()) {
 | |
|     Redirect(q->value(16).toString());
 | |
|     delete q;
 | |
|     exit(0);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Generate CGI Header
 | |
|   //
 | |
|   printf("Content-type: application/rss+xml; charset=UTF-8\n\n");
 | |
|   
 | |
|   //
 | |
|   // Render Header XML
 | |
|   //
 | |
|   printf("%s\n",(const char *)q->value(9).toString().utf8());
 | |
| 
 | |
|   //
 | |
|   // Render Channel XML
 | |
|   //
 | |
|   printf("<channel>\n");
 | |
|   printf("%s\n",(const char *)ResolveChannelWildcards(q).utf8());
 | |
| 
 | |
|   //
 | |
|   // Render Item XML
 | |
|   //
 | |
|   sql=QString("select ")+
 | |
|     "ITEM_TITLE,"+          // 00
 | |
|     "ITEM_DESCRIPTION,"+    // 01
 | |
|     "ITEM_CATEGORY,"+       // 02
 | |
|     "ITEM_LINK,"+           // 03
 | |
|     "ITEM_AUTHOR,"+         // 04
 | |
|     "ITEM_SOURCE_TEXT,"+    // 05
 | |
|     "ITEM_SOURCE_URL,"+     // 06
 | |
|     "ITEM_COMMENTS,"+       // 07
 | |
|     "AUDIO_FILENAME,"+      // 08
 | |
|     "AUDIO_LENGTH,"+        // 09
 | |
|     "AUDIO_TIME,"+          // 10
 | |
|     "EFFECTIVE_DATETIME,"+  // 11
 | |
|     "ID "+                  // 12
 | |
|     "from PODCASTS where "+
 | |
|     QString().sprintf("(FEED_ID=%d)&&",q->value(13).toUInt())+
 | |
|     QString().sprintf("(STATUS=%d) ",RDPodcast::StatusActive)+
 | |
|     "order by ORIGIN_DATETIME";
 | |
|   if(q->value(15).toString()=="N") {
 | |
|     sql+=" desc";
 | |
|   }
 | |
|   q1=new RDSqlQuery(sql);
 | |
|   while(q1->next()) {
 | |
|     printf("<item>\n");
 | |
|     printf("%s\n",(const char *)ResolveItemWildcards(keyname,q1,q).utf8());
 | |
|     printf("</item>\n");
 | |
|   }
 | |
|   delete q1;
 | |
| 
 | |
|   printf("</channel>\n");
 | |
|   printf("</rss>\n");
 | |
|   delete q;
 | |
| 
 | |
|   exit(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| void MainObject::ServeLink(const char *keyname,int cast_id,bool count)
 | |
| {
 | |
|   QString sql;
 | |
|   RDSqlQuery *q;
 | |
| 
 | |
|   sql=QString("select ")+
 | |
|     "FEEDS.BASE_URL,"+           // 00
 | |
|     "PODCASTS.AUDIO_FILENAME "+  // 01
 | |
|     "from FEEDS left join PODCASTS "+
 | |
|     "on FEEDS.ID=PODCASTS.FEED_ID where "+
 | |
|     "(FEEDS.KEY_NAME=\""+RDEscapeString(keyname)+"\")&&"+
 | |
|     QString().sprintf("(PODCASTS.ID=%d)",cast_id);
 | |
|   q=new RDSqlQuery(sql);
 | |
|   if(!q->first()) {
 | |
|     delete q;
 | |
|     RDCgiError("Unable to retrieve cast record!");
 | |
|   }
 | |
|   if(count) {
 | |
|     RDIncrementCastCount(keyname,cast_id);
 | |
|   }
 | |
|   printf("Content-type: audio/x-mpeg\n");
 | |
|   printf("Location: %s/%s\n\n",(const char *)q->value(0).toString(),
 | |
| 	 (const char *)q->value(1).toString().utf8());
 | |
|   delete q;
 | |
| 
 | |
|   exit(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| QString MainObject::ResolveChannelWildcards(RDSqlQuery *chan_q)
 | |
| {
 | |
|   QString ret=chan_q->value(10).toString();
 | |
|   //  ret.replace("%TITLE%",chan_q->value(0).toString());
 | |
|   ret.replace("%TITLE%",RDXmlEscape(chan_q->value(0).toString()));
 | |
|   ret.replace("%DESCRIPTION%",RDXmlEscape(chan_q->value(1).toString()));
 | |
|   ret.replace("%CATEGORY%",RDXmlEscape(chan_q->value(2).toString()));
 | |
|   ret.replace("%LINK%",RDXmlEscape(chan_q->value(3).toString()));
 | |
|   ret.replace("%COPYRIGHT%",RDXmlEscape(chan_q->value(4).toString()));
 | |
|   ret.replace("%WEBMASTER%",RDXmlEscape(chan_q->value(5).toString()));
 | |
|   ret.replace("%LANGUAGE%",RDXmlEscape(chan_q->value(6).toString()));
 | |
|   ret.replace("%BUILD_DATE%",chan_q->value(7).toDateTime().
 | |
| 	      toString("ddd, d MMM yyyy hh:mm:ss ")+"GMT");
 | |
|   ret.replace("%PUBLISH_DATE%",chan_q->value(8).toDateTime().
 | |
| 	      toString("ddd, d MMM yyyy hh:mm:ss ")+"GMT");
 | |
|   ret.replace("%GENERATOR%",QString("Rivendell ")+VERSION);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| QString MainObject::ResolveItemWildcards(const QString &keyname,
 | |
| 					 RDSqlQuery *item_q,RDSqlQuery *chan_q)
 | |
| {
 | |
|   RDFeed *feed=new RDFeed(keyname,rda->config());
 | |
|   QString ret=chan_q->value(11).toString();
 | |
|   ret.replace("%ITEM_TITLE%",RDXmlEscape(item_q->value(0).toString()));
 | |
|   ret.replace("%ITEM_DESCRIPTION%",
 | |
| 	      RDXmlEscape(item_q->value(1).toString()));
 | |
|   ret.replace("%ITEM_CATEGORY%",
 | |
| 	      RDXmlEscape(item_q->value(2).toString()));
 | |
|   ret.replace("%ITEM_LINK%",RDXmlEscape(item_q->value(3).toString()));
 | |
|   ret.replace("%ITEM_AUTHOR%",RDXmlEscape(item_q->value(4).toString()));
 | |
|   ret.replace("%ITEM_SOURCE_TEXT%",
 | |
| 	      RDXmlEscape(item_q->value(5).toString()));
 | |
|   ret.replace("%ITEM_SOURCE_URL%",
 | |
| 	      RDXmlEscape(item_q->value(6).toString()));
 | |
|   ret.replace("%ITEM_COMMENTS%",
 | |
| 	      RDXmlEscape(item_q->value(7).toString()));
 | |
|   ret.replace("%ITEM_AUDIO_URL%",
 | |
| 	      (const char *)RDXmlEscape(feed->
 | |
| 	        audioUrl(RDFeed::LinkCounted,server_name,
 | |
| 			 item_q->value(12).toUInt())));
 | |
|   ret.replace("%ITEM_AUDIO_LENGTH%",item_q->value(9).toString());
 | |
|   ret.replace("%ITEM_AUDIO_TIME%",
 | |
| 	      RDGetTimeLength(item_q->value(10).toInt(),false,false));
 | |
|   ret.replace("%ITEM_PUBLISH_DATE%",item_q->value(11).toDateTime().
 | |
| 	      toString("ddd, d MMM yyyy hh:mm:ss ")+"GMT");
 | |
|   ret.replace("%ITEM_GUID%",RDPodcast::guid(chan_q->value(12).toString(),
 | |
| 					    item_q->value(8).toString(),
 | |
| 					    chan_q->value(11).toUInt(),
 | |
| 					    item_q->value(12).toUInt()));
 | |
|   delete feed;
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool MainObject::ShouldCount(const QString &hdr)
 | |
| {
 | |
|   bool ret=false;
 | |
|   QStringList lines=hdr.split("\n");
 | |
|   int n;
 | |
|   QString str;
 | |
| 
 | |
|   for(int i=0;i<lines.size();i++) {
 | |
|     if((n=lines[i].find("="))>0) {
 | |
|       if(lines[i].left(n).lower()=="bytes") {
 | |
| 	str=lines[i].right(lines[i].length()-n-1).stripWhiteSpace();
 | |
| 	n=str.find("-");
 | |
| 	if(n==0) {
 | |
| 	  ret=true;
 | |
| 	}
 | |
| 	if(n>0) {
 | |
| 	  if(str.left(n)=="0") {
 | |
| 	    ret=true;
 | |
| 	  }
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| void MainObject::Redirect(const QString &url)
 | |
| {
 | |
|   printf("Status: 301 Moved Permanently\n");
 | |
|   printf("Location: %s\n",(const char *)url.utf8());
 | |
|   printf("Content-type: text/html\n");
 | |
|   printf("\n");
 | |
|   printf("The feed has been relocated to %s.\n",(const char *)url.utf8());
 | |
| }
 | |
| 
 | |
| 
 | |
| int main(int argc,char *argv[])
 | |
| {
 | |
|   QApplication a(argc,argv,false);
 | |
|   new MainObject();
 | |
|   return a.exec();
 | |
| }
 |