// webget.cpp // // Rivendell download utility // // (C) Copyright 2018 Fred Gleason // // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "webget.h" MainObject::MainObject(QObject *parent) :QObject(parent) { QString err_msg; // // Open the Database // rda=new RDApplication("webget.cgi","webget.cgi",WEBGET_CGI_USAGE,this); if(!rda->open(&err_msg)) { XmlExit(err_msg,500,"webget.cpp",LINE_NUMBER); } // // Read Command Options // for(unsigned i=0;icmdSwitch()->keys();i++) { if(!rda->cmdSwitch()->processed(i)) { XmlExit("unknown command option",500,"webget.cpp",LINE_NUMBER); } } // // Drop root permissions // if(setgid(rda->config()->gid())<0) { XmlExit("Unable to set Rivendell group",500,"webget.cpp",LINE_NUMBER); } if(setuid(rda->config()->uid())<0) { XmlExit("Unable to set Rivendell user",500,"webget.cpp",LINE_NUMBER); } if(getuid()==0) { XmlExit("Rivendell user should never be \"root\"!",500,"webget.cpp", LINE_NUMBER); } // // Determine Connection Type // if(getenv("REQUEST_METHOD")==NULL) { XmlExit("missing REQUEST_METHOD",500,"webget.cpp",LINE_NUMBER); } if(QString(getenv("REQUEST_METHOD")).lower()!="post") { XmlExit("invalid web method",500,"webget.cpp",LINE_NUMBER); } if(getenv("REMOTE_ADDR")!=NULL) { webget_remote_address.setAddress(getenv("REMOTE_ADDR")); } if(getenv("REMOTE_HOST")!=NULL) { webget_remote_hostname=getenv("REMOTE_HOST"); } if(webget_remote_hostname.isEmpty()) { webget_remote_hostname=webget_remote_address.toString(); } // // Generate Post // webget_post=new RDFormPost(RDFormPost::AutoEncoded,false); if(webget_post->error()!=RDFormPost::ErrorOk) { XmlExit(webget_post->errorString(webget_post->error()),400,"webget.cpp", LINE_NUMBER); Exit(0); } // // Authenticate Connection // if(!Authenticate()) { printf("Content-type: text/html\n"); printf("Status: 400\n"); printf("\n"); printf("Invalid User name or Password!\n"); Exit(0); } // // Connect to ripcd(8) // connect(rda->ripc(),SIGNAL(connected(bool)), this,SLOT(ripcConnectedData(bool))); rda->ripc()-> connectHost("localhost",RIPCD_TCP_PORT,rda->config()->password()); } void MainObject::ripcConnectedData(bool state) { if(!state) { XmlExit("unable to connect to ripc service",500,"webget.cpp",LINE_NUMBER); Exit(0); } QString title; if(!webget_post->getValue("title",&title)) { XmlExit("missing \"title\"",400,"webget.cpp",LINE_NUMBER); } int samprate; if(!webget_post->getValue("samprate",&samprate)) { XmlExit("missing \"samprate\"",400,"webget.cpp",LINE_NUMBER); } int format; if(!webget_post->getValue("format",&format)) { XmlExit("missing \"format\"",400,"webget.cpp",LINE_NUMBER); } int bitrate=0; if((format==RDSettings::MpegL2)||(format==RDSettings::MpegL3)) { if(!webget_post->getValue("bitrate",&bitrate)) { XmlExit("missing \"bitrate\"",400,"webget.cpp",LINE_NUMBER); } } int quality=0; if(format==RDSettings::OggVorbis) { if(!webget_post->getValue("quality",&quality)) { XmlExit("missing \"quality\"",400,"webget.cpp",LINE_NUMBER); } } unsigned cartnum=0; int cutnum=0; QString sql=QString("select ")+ "CUTS.CUT_NAME from "+ "CART left join CUTS on CART.NUMBER=CUTS.CART_NUMBER where "+ "CART.TITLE=\""+RDEscapeString(title)+"\" && "+ QString().sprintf("CART.TYPE=%d",RDCart::Audio); RDSqlQuery *q=new RDSqlQuery(sql); if(q->first()) { cartnum=RDCut::cartNumber(q->value(0).toString()); cutnum=RDCut::cutNumber(q->value(0).toString()); } delete q; if(cartnum==0) { printf("Content-type: text/html\n"); printf("Status: 400\n"); printf("\n"); printf("No such cart!\n"); Exit(0); } // // Audio Settings // RDSettings *settings=new RDSettings(); settings->setFormat((RDSettings::Format)format); settings->setChannels(1); settings->setSampleRate(samprate); settings->setBitRate(bitrate); settings->setQuality(quality); settings->setNormalizationLevel(-100); // // Generate Metadata // RDCart *cart=new RDCart(cartnum); RDCut *cut=new RDCut(cartnum,cutnum); RDWaveData *wavedata=NULL; QString rdxl; float speed_ratio=1.0; wavedata=new RDWaveData(); if(wavedata!=NULL) { cart->getMetadata(wavedata); cut->getMetadata(wavedata); if(cart->enforceLength()) { speed_ratio=(float)cut->length()/(float)cart->forcedLength(); } rdxl=cart->xml(true,false,settings,cutnum); } // // Export Cut // int fd; ssize_t n; uint8_t data[2048]; QString err_msg; RDTempDirectory *tempdir=new RDTempDirectory("rdxport-export"); if(!tempdir->create(&err_msg)) { XmlExit("unable to create temporary directory ["+err_msg+"]",500); } QString tmpfile=tempdir->path()+"/exported_audio"; RDAudioConvert *conv=new RDAudioConvert(this); conv->setSourceFile(RDCut::pathName(cartnum,cutnum)); conv->setDestinationFile(tmpfile); conv->setDestinationSettings(settings); conv->setDestinationWaveData(wavedata); conv->setDestinationRdxl(rdxl); conv->setRange(cut->startPoint(),cut->endPoint()); conv->setSpeedRatio(speed_ratio); delete cut; delete cart; RDAudioConvert::ErrorCode conv_err; int resp_code=0; switch(conv_err=conv->convert()) { case RDAudioConvert::ErrorOk: switch(settings->format()) { case RDSettings::Pcm16: case RDSettings::Pcm24: printf("Content-type: audio/x-wav\n\n"); break; case RDSettings::MpegL1: case RDSettings::MpegL2: case RDSettings::MpegL2Wav: case RDSettings::MpegL3: printf("Content-type: audio/x-mpeg\n\n"); break; case RDSettings::OggVorbis: printf("Content-type: audio/ogg\n\n"); break; case RDSettings::Flac: printf("Content-type: audio/flac\n\n"); break; } fflush(NULL); if((fd=open(tmpfile,O_RDONLY))>=0) { while((n=read(fd,data,2048))>0) { write(1,data,n); } } close(fd); unlink(tmpfile); delete tempdir; Exit(0); break; case RDAudioConvert::ErrorFormatNotSupported: case RDAudioConvert::ErrorInvalidSettings: resp_code=415; break; case RDAudioConvert::ErrorNoSource: resp_code=404; break; case RDAudioConvert::ErrorNoDestination: case RDAudioConvert::ErrorInvalidSource: case RDAudioConvert::ErrorNoSpace: case RDAudioConvert::ErrorInternal: case RDAudioConvert::ErrorNoDisc: case RDAudioConvert::ErrorNoTrack: case RDAudioConvert::ErrorInvalidSpeed: case RDAudioConvert::ErrorFormatError: resp_code=500; break; } delete conv; delete settings; if(wavedata!=NULL) { delete wavedata; } delete tempdir; if(resp_code==200) { Exit(200); } else { XmlExit(RDAudioConvert::errorText(conv_err),resp_code,"export.cpp", LINE_NUMBER,conv_err); } } bool MainObject::Authenticate() { QString name; QString passwd; if(!webget_post->getValue("LOGIN_NAME",&name)) { return false; } if(!webget_post->getValue("PASSWORD",&passwd)) { return false; } RDUser *user=new RDUser(name); if((!user->exists())|| (!user->checkPassword(passwd,false))|| (!user->webgetLogin())) { return false; } return true; } void MainObject::Exit(int code) { if(webget_post!=NULL) { delete webget_post; } exit(code); } void MainObject::XmlExit(const QString &str,int code,const QString &srcfile, int srcline,RDAudioConvert::ErrorCode err) { if(webget_post!=NULL) { delete webget_post; } #ifdef RDXPORT_DEBUG if(srcline>0) { RDXMLResult(str+" \""+srcfile+"\" "+QString().sprintf("line %d",srcline), code,err); } else { RDXMLResult(str,code,err); } #else RDXMLResult(str,code,err); #endif // RDXPORT_DEBUG exit(0); } int main(int argc,char *argv[]) { QApplication a(argc,argv,false); new MainObject(); return a.exec(); }