MythTV  0.26-pre
httprequest.cpp
Go to the documentation of this file.
00001 
00002 // Program Name: httprequest.cpp
00003 // Created     : Oct. 21, 2005
00004 //
00005 // Purpose     : Http Request/Response
00006 //                                                                            
00007 // Copyright (c) 2005 David Blain <dblain@mythtv.org>
00008 //                                          
00009 // Licensed under the GPL v2 or later, see COPYING for details                    
00010 //
00012 
00013 #include "httprequest.h"
00014 
00015 #include <QFile>
00016 #include <QFileInfo>
00017 #include <QTextCodec>
00018 #include <QStringList>
00019 #include <QCryptographicHash>
00020 #include <QDateTime>
00021 
00022 #include "mythconfig.h"
00023 #if !( CONFIG_DARWIN || CONFIG_CYGWIN || defined(__FreeBSD__) || defined(USING_MINGW))
00024 #define USE_SETSOCKOPT
00025 #include <sys/sendfile.h>
00026 #endif
00027 #include <sys/types.h>
00028 #include <sys/stat.h>
00029 #include <stdlib.h>
00030 #include <fcntl.h>
00031 #include <cerrno>
00032 
00033 #ifndef USING_MINGW
00034 #include <netinet/tcp.h>
00035 #endif
00036 
00037 #include "upnp.h"
00038 
00039 #include "compat.h"
00040 #include "mythlogging.h"
00041 #include "mythversion.h"
00042 
00043 #include "serializers/xmlSerializer.h"
00044 #include "serializers/soapSerializer.h"
00045 #include "serializers/jsonSerializer.h"
00046 #include "serializers/xmlplistSerializer.h"
00047 
00048 #ifndef O_LARGEFILE
00049 #define O_LARGEFILE 0
00050 #endif
00051 
00052 static MIMETypes g_MIMETypes[] =
00053 {
00054     { "gif" , "image/gif"                  },
00055     { "jpg" , "image/jpeg"                 },
00056     { "jpeg", "image/jpeg"                 },
00057     { "png" , "image/png"                  },
00058     { "htm" , "text/html"                  },
00059     { "html", "text/html"                  },
00060     { "qsp" , "text/html"                  },
00061     { "js"  , "application/javascript"     },
00062     { "qjs" , "application/javascript"     },
00063     { "txt" , "text/plain"                 },
00064     { "xml" , "text/xml"                   },
00065     { "xslt", "text/xml"                   },
00066     { "pdf" , "application/pdf"            },
00067     { "avi" , "video/avi"                  },
00068     { "css" , "text/css"                   },
00069     { "swf" , "application/x-shockwave-flash" },
00070     { "xls" , "application/vnd.ms-excel"   },
00071     { "doc" , "application/vnd.ms-word"    },
00072     { "mid" , "audio/midi"                 },
00073     { "mp3" , "audio/mpeg"                 },
00074     { "rm"  , "application/vnd.rn-realmedia" },
00075     { "wav" , "audio/wav"                  },
00076     { "zip" , "application/x-tar"          },
00077     { "gz"  , "application/x-tar"          },
00078     { "mpg" , "video/mpeg"                 },
00079     { "mpg2", "video/mpeg"                 },
00080     { "mpeg", "video/mpeg"                 },
00081     { "mpeg2","video/mpeg"                 },
00082     { "vob" , "video/mpeg"                 },
00083     { "asf" , "video/x-ms-asf"             },
00084     { "nuv" , "video/nupplevideo"          },
00085     { "mov" , "video/quicktime"            },
00086     { "mp4" , "video/mp4"                  },
00087     // This formerly was video/x-matroska, but got changed due to #8643
00088     { "mkv" , "video/x-mkv"                },
00089     { "mka" , "audio/x-matroska"           },
00090     { "wmv" , "video/x-ms-wmv"             },
00091     // Defined: http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions
00092     { "ogg" , "audio/ogg"                  },
00093     { "ogv" , "video/ogg"                  },
00094     { "ogx" , "application/ogg"            },
00095     { "oga" , "audio/ogg"                  },
00096     // Similarly, this could be audio/flac or application/flac:
00097     { "flac", "audio/x-flac"               },
00098     { "m4a" , "audio/x-m4a"                },
00099     // HTTP Live Streaming
00100     { "m3u8", "application/x-mpegurl"      },
00101     { "ts"  , "video/mp2t"                 },
00102 };
00103 
00104 static const char *Static401Error = 
00105     "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
00106         "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">"
00107     "<HTML>"
00108       "<HEAD>"
00109         "<TITLE>Error</TITLE>"
00110         "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
00111       "</HEAD>"
00112       "<BODY><H1>401 Unauthorized.</H1></BODY>"
00113     "</HTML>";
00114 
00115 static const int g_nMIMELength = sizeof( g_MIMETypes) / sizeof( MIMETypes );
00116 static const int g_on          = 1;
00117 static const int g_off         = 0;
00118 
00119 const char *HTTPRequest::m_szServerHeaders = "Accept-Ranges: bytes\r\n";
00120 
00122 //
00124 
00125 HTTPRequest::HTTPRequest() : m_procReqLineExp ( "[ \r\n][ \r\n]*"  ),
00126                              m_parseRangeExp  ( "(\\d|\\-)"        ),
00127                              m_eType          ( RequestTypeUnknown ),
00128                              m_eContentType   ( ContentType_Unknown),
00129                              m_nMajor         (   0 ),
00130                              m_nMinor         (   0 ),
00131                              m_bSOAPRequest   ( false ),
00132                              m_eResponseType  ( ResponseTypeUnknown),
00133                              m_nResponseStatus( 200 ),
00134                              m_pPostProcess   ( NULL )
00135 {
00136     m_response.open( QIODevice::ReadWrite );
00137 }
00138 
00140 //
00142 
00143 RequestType HTTPRequest::SetRequestType( const QString &sType )
00144 {
00145     if (sType == "GET"        ) return( m_eType = RequestTypeGet         );
00146     if (sType == "HEAD"       ) return( m_eType = RequestTypeHead        );
00147     if (sType == "POST"       ) return( m_eType = RequestTypePost        );
00148     if (sType == "M-SEARCH"   ) return( m_eType = RequestTypeMSearch     );
00149 
00150     if (sType == "SUBSCRIBE"  ) return( m_eType = RequestTypeSubscribe   );
00151     if (sType == "UNSUBSCRIBE") return( m_eType = RequestTypeUnsubscribe );
00152     if (sType == "NOTIFY"     ) return( m_eType = RequestTypeNotify      );
00153 
00154     if (sType.startsWith( QString("HTTP/") )) return( m_eType = RequestTypeResponse );
00155 
00156     LOG(VB_UPNP, LOG_INFO,
00157         QString("HTTPRequest::SentRequestType( %1 ) - returning Unknown.")
00158             .arg(sType));
00159 
00160     return( m_eType = RequestTypeUnknown);
00161 }
00162 
00164 //
00166 
00167 QString HTTPRequest::BuildHeader( long long nSize )
00168 {
00169     QString sHeader;
00170     QString sContentType = (m_eResponseType == ResponseTypeOther) ?
00171                             m_sResponseTypeText : GetResponseType();
00172 
00173     sHeader = QString( "HTTP/%1.%2 %3\r\n"
00174                        "Date: %4\r\n"
00175                        "Server: %5, UPnP/1.0, MythTV %6\r\n" )
00176         .arg(m_nMajor).arg(m_nMinor).arg(GetResponseStatus())
00177         .arg(QDateTime::currentDateTime().toString("d MMM yyyy hh:mm:ss"))
00178         .arg(HttpServer::GetPlatform()).arg(MYTH_BINARY_VERSION);
00179 
00180     sHeader += GetAdditionalHeaders();
00181 
00182     sHeader += QString( "Connection: %1\r\n"
00183                         "Content-Type: %2\r\n"
00184                         "Content-Length: %3\r\n" )
00185                         .arg( GetKeepAlive() ? "Keep-Alive" : "Close" )
00186                         .arg( sContentType )
00187                         .arg( nSize );
00188 
00189     // ----------------------------------------------------------------------
00190     // Temp Hack to process DLNA header
00191                              
00192     QString sValue = GetHeaderValue( "getcontentfeatures.dlna.org", "0" );
00193 
00194     if (sValue == "1")
00195         sHeader += "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0;"
00196                    "DLNA.ORG_FLAGS=01500000000000000000000000000000\r\n";
00197 
00198     // ----------------------------------------------------------------------
00199 
00200     sHeader += "\r\n";
00201 
00202     return sHeader;
00203 }
00204 
00206 //
00208 
00209 long HTTPRequest::SendResponse( void )
00210 {
00211     long      nBytes    = 0;
00212 
00213     switch( m_eResponseType )
00214     {
00215         case ResponseTypeUnknown:
00216         case ResponseTypeNone:
00217             LOG(VB_UPNP, LOG_INFO,
00218                 QString("HTTPRequest::SendResponse( None ) :%1 -> %2:")
00219                     .arg(GetResponseStatus()) .arg(GetPeerAddress()));
00220             return( -1 );
00221 
00222         case ResponseTypeFile:
00223             LOG(VB_UPNP, LOG_INFO,
00224                 QString("HTTPRequest::SendResponse( File ) :%1 -> %2:")
00225                     .arg(GetResponseStatus()) .arg(GetPeerAddress()));
00226             return( SendResponseFile( m_sFileName ));
00227 
00228         case ResponseTypeXML:
00229         case ResponseTypeHTML:
00230         case ResponseTypeOther:
00231         default:
00232             break;
00233     }
00234 
00235     LOG(VB_UPNP, LOG_INFO,
00236         QString("HTTPRequest::SendResponse(xml/html) (%1) :%2 -> %3: %4")
00237              .arg(m_sFileName) .arg(GetResponseStatus())
00238              .arg(GetPeerAddress()) .arg(m_eResponseType));
00239 
00240     // ----------------------------------------------------------------------
00241     // Make it so the header is sent with the data
00242     // ----------------------------------------------------------------------
00243 
00244 #ifdef USE_SETSOCKOPT
00245     // Never send out partially complete segments
00246     setsockopt( getSocketHandle(), SOL_TCP, TCP_CORK, &g_on, sizeof( g_on ));
00247 #endif
00248 
00249     // ----------------------------------------------------------------------
00250     // Check for ETag match...
00251     // ----------------------------------------------------------------------
00252 
00253     QString sETag = GetHeaderValue( "If-None-Match", "" );
00254 
00255     if ( !sETag.isEmpty() && sETag == m_mapRespHeaders[ "ETag" ] )
00256     {
00257         LOG(VB_UPNP, LOG_INFO,
00258             QString("HTTPRequest::SendResponse(%1) - Cached")
00259                 .arg(sETag));
00260 
00261         m_nResponseStatus = 304;
00262 
00263         // no content can be returned.
00264         m_response.buffer().clear();
00265     }
00266 
00267     // ----------------------------------------------------------------------
00268 
00269     int nContentLen = m_response.buffer().length();
00270 
00271     QBuffer *pBuffer = &m_response;
00272 
00273     // ----------------------------------------------------------------------
00274     // Should we try to return data gzip'd?
00275     // ----------------------------------------------------------------------
00276 
00277     QBuffer compBuffer;
00278 
00279     if (( nContentLen > 0 ) && m_mapHeaders[ "accept-encoding" ].contains( "gzip" ))
00280     {
00281         QByteArray compressed = gzipCompress( m_response.buffer() );
00282         compBuffer.setData( compressed );
00283 
00284         if (compBuffer.buffer().length() > 0)
00285         {
00286             pBuffer = &compBuffer;
00287 
00288             m_mapRespHeaders[ "Content-Encoding" ] = "gzip";
00289         }
00290     }
00291 
00292     // ----------------------------------------------------------------------
00293     // Write out Header.
00294     // ----------------------------------------------------------------------
00295 
00296     nContentLen = pBuffer->buffer().length();
00297 
00298     QString    rHeader = BuildHeader( nContentLen );
00299 
00300     QByteArray sHeader = rHeader.toUtf8();
00301     nBytes  = WriteBlockDirect( sHeader.constData(), sHeader.length() );
00302 
00303     // ----------------------------------------------------------------------
00304     // Write out Response buffer.
00305     // ----------------------------------------------------------------------
00306 
00307     if (( m_eType != RequestTypeHead ) && ( nContentLen > 0 ))
00308     {
00309         nBytes += SendData( pBuffer, 0, nContentLen );
00310     }
00311 
00312     // ----------------------------------------------------------------------
00313     // Turn off the option so any small remaining packets will be sent
00314     // ----------------------------------------------------------------------
00315 
00316 #ifdef USE_SETSOCKOPT
00317     setsockopt( getSocketHandle(), SOL_TCP, TCP_CORK, &g_off, sizeof( g_off ));
00318 #endif
00319 
00320     return( nBytes );
00321 }
00322 
00324 //
00326 
00327 long HTTPRequest::SendResponseFile( QString sFileName )
00328 {
00329     long        nBytes  = 0;
00330     long long   llSize  = 0;
00331     long long   llStart = 0;
00332     long long   llEnd   = 0;
00333 
00334     LOG(VB_UPNP, LOG_INFO, QString("SendResponseFile ( %1 )").arg(sFileName));
00335 
00336     m_eResponseType     = ResponseTypeOther;
00337     m_sResponseTypeText = "text/plain";
00338 
00339 #if 0
00340     // Dump request header
00341     for ( QStringMap::iterator it  = m_mapHeaders.begin();
00342                                it != m_mapHeaders.end();
00343                              ++it )
00344     {
00345         LOG(VB_GENERAL, LOG_DEBUG, it.key() + ": " + *it);
00346     }
00347 #endif
00348 
00349     // ----------------------------------------------------------------------
00350     // Make it so the header is sent with the data
00351     // ----------------------------------------------------------------------
00352 
00353 #ifdef USE_SETSOCKOPT
00354     // Never send out partially complete segments
00355     setsockopt( getSocketHandle(), SOL_TCP, TCP_CORK, &g_on, sizeof( g_on ));
00356 #endif
00357 
00358     QFile tmpFile( sFileName );
00359     if (tmpFile.exists( ) && tmpFile.open( QIODevice::ReadOnly ))
00360     {
00361 
00362         m_sResponseTypeText = TestMimeType( sFileName );
00363 
00364         // ------------------------------------------------------------------
00365         // Get File size
00366         // ------------------------------------------------------------------
00367 
00368         llSize = llEnd = tmpFile.size( );
00369 
00370         m_nResponseStatus = 200;
00371 
00372         // ------------------------------------------------------------------
00373         // Process any Range Header
00374         // ------------------------------------------------------------------
00375 
00376         bool    bRange = false;
00377         QString sRange = GetHeaderValue( "range", "" );
00378 
00379         if (sRange.length() > 0)
00380         {
00381             bRange = ParseRange( sRange, llSize, &llStart, &llEnd );
00382 
00383             // Adjust ranges that are too long.  
00384 
00385             if (llEnd >= llSize) 
00386                 llEnd = llSize-1; 
00387 
00388             if ((llSize > llStart) && (llSize > llEnd) && (llEnd > llStart))
00389             {
00390                 if (bRange)
00391                 {
00392                     m_nResponseStatus = 206;
00393                     m_mapRespHeaders[ "Content-Range" ] = QString("bytes %1-%2/%3")
00394                                                               .arg( llStart )
00395                                                               .arg( llEnd   )
00396                                                               .arg( llSize  );
00397                     llSize = (llEnd - llStart) + 1;
00398                 }
00399             }
00400             else
00401             {
00402                 m_nResponseStatus = 416;
00403                 llSize = 0;
00404                 LOG(VB_UPNP, LOG_INFO,
00405                     QString("HTTPRequest::SendResponseFile(%1) - "
00406                             "invalid byte range %2-%3/%4")
00407                             .arg(sFileName) .arg(llStart) .arg(llEnd)
00408                             .arg(llSize));
00409             }
00410         }
00411 
00412         // DSM-?20 specific response headers
00413         if (bRange == false)
00414             m_mapRespHeaders[ "User-Agent"    ] = "redsonic";
00415 
00416         // ------------------------------------------------------------------
00417         //
00418         // ------------------------------------------------------------------
00419 
00420     }
00421     else
00422     {
00423         LOG(VB_UPNP, LOG_INFO,
00424             QString("HTTPRequest::SendResponseFile(%1) - cannot find file!")
00425                 .arg(sFileName));
00426         m_nResponseStatus = 404;
00427     }
00428 
00429     // -=>TODO: Should set "Content-Length: *" if file is still recording
00430 
00431     // ----------------------------------------------------------------------
00432     // Write out Header.
00433     // ----------------------------------------------------------------------
00434 
00435     QString    rHeader = BuildHeader( llSize );
00436     QByteArray sHeader = rHeader.toUtf8();
00437     nBytes = WriteBlockDirect( sHeader.constData(), sHeader.length() );
00438 
00439     // ----------------------------------------------------------------------
00440     // Write out File.
00441     // ----------------------------------------------------------------------
00442 
00443 #if 0
00444     LOG(VB_UPNP, LOG_DEBUG,
00445         QString("SendResponseFile : size = %1, start = %2, end = %3")
00446             .arg(llSize).arg(llStart).arg(llEnd));
00447 #endif
00448     if (( m_eType != RequestTypeHead ) && (llSize != 0))
00449     {
00450         long long sent = SendFile( tmpFile, llStart, llSize );
00451 
00452         if (sent == -1)
00453         {
00454             LOG(VB_UPNP, LOG_INFO,
00455                 QString("SendResponseFile( %1 ) Error: %2 [%3]" )
00456                     .arg(sFileName) .arg(errno) .arg(strerror(errno)));
00457 
00458             nBytes = -1;
00459         }
00460     }
00461 
00462     // ----------------------------------------------------------------------
00463     // Turn off the option so any small remaining packets will be sent
00464     // ----------------------------------------------------------------------
00465 
00466 #ifdef USE_SETSOCKOPT
00467     setsockopt( getSocketHandle(), SOL_TCP, TCP_CORK, &g_off, sizeof( g_off ));
00468 #endif
00469 
00470     // -=>TODO: Only returns header length...
00471     //          should we change to return total bytes?
00472 
00473     return nBytes;
00474 }
00475 
00477 //
00479 
00480 #define SENDFILE_BUFFER_SIZE 65536
00481 
00482 qint64 HTTPRequest::SendData( QIODevice *pDevice, qint64 llStart, qint64 llBytes )
00483 {
00484     bool   bShouldClose = false;
00485     qint64 sent = 0;
00486 
00487     if (!pDevice->isOpen())
00488     {
00489         pDevice->open( QIODevice::ReadOnly );
00490         bShouldClose = true;
00491     }
00492 
00493     // ----------------------------------------------------------------------
00494     // Set out file position to requested start location.
00495     // ----------------------------------------------------------------------
00496 
00497     if ( pDevice->seek( llStart ) == false)
00498         return -1;
00499 
00500     char   aBuffer[ SENDFILE_BUFFER_SIZE ];
00501 
00502     qint64 llBytesRemaining = llBytes;
00503     qint64 llBytesToRead    = 0;
00504     qint64 llBytesRead      = 0;
00505     qint64 llBytesWritten   = 0;
00506 
00507     while ((sent < llBytes) && !pDevice->atEnd())
00508     {
00509         llBytesToRead  = std::min( (qint64)SENDFILE_BUFFER_SIZE, llBytesRemaining );
00510         
00511         if (( llBytesRead = pDevice->read( aBuffer, llBytesToRead )) != -1 )
00512         {
00513             if (( llBytesWritten = WriteBlockDirect( aBuffer, llBytesRead )) == -1)
00514                 return -1;
00515 
00516             // -=>TODO: We don't handle the situation where we read more than was sent.
00517 
00518             sent             += llBytesRead;
00519             llBytesRemaining -= llBytesRead;
00520         }
00521     }
00522 
00523     if (bShouldClose)
00524         pDevice->close();
00525 
00526     return sent;
00527 }
00528 
00530 // 
00532 
00533 qint64 HTTPRequest::SendFile( QFile &file, qint64 llStart, qint64 llBytes )
00534 {
00535     qint64 sent = 0;
00536 
00537 #ifndef __linux__
00538     sent = SendData( (QIODevice *)(&file), llStart, llBytes );
00539 #else
00540     __off64_t  offset = llStart; 
00541     int        fd     = file.handle( ); 
00542   
00543     if ( fd == -1 ) 
00544     { 
00545         LOG(VB_UPNP, LOG_INFO,
00546             QString("SendResponseFile( %1 ) Error: %2 [%3]") 
00547                 .arg(file.fileName()) .arg(file.error()) 
00548                 .arg(strerror(file.error()))); 
00549         sent = -1; 
00550     } 
00551     else 
00552     { 
00553         qint64     llSent = 0;
00554 
00555         do 
00556         { 
00557             // SSIZE_MAX should work in kernels 2.6.16 and later. 
00558             // The loop is needed in any case. 
00559   
00560             sent = sendfile64(getSocketHandle(), fd, &offset, 
00561                               (size_t)MIN(llBytes, INT_MAX)); 
00562   
00563             if (sent >= 0)
00564             {
00565                 llBytes -= sent; 
00566                 llSent  += sent;
00567                 LOG(VB_UPNP, LOG_INFO,
00568                     QString("SendResponseFile : --- size = %1, "
00569                             "offset = %2, sent = %3") 
00570                         .arg(llBytes).arg(offset).arg(sent)); 
00571             }
00572         } 
00573         while (( sent >= 0 ) && ( llBytes > 0 )); 
00574 
00575         sent = llSent;
00576     } 
00577 #endif
00578 
00579     return( sent );
00580 }
00581 
00582 
00584 //
00586 
00587 void HTTPRequest::FormatErrorResponse( bool  bServerError,
00588                                        const QString &sFaultString,
00589                                        const QString &sDetails )
00590 {
00591     m_eResponseType   = ResponseTypeXML;
00592     m_nResponseStatus = 500;
00593 
00594     QTextStream stream( &m_response );
00595 
00596     stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
00597 
00598     QString sWhere = ( bServerError ) ? "s:Server" : "s:Client";
00599 
00600     if (m_bSOAPRequest)
00601     {
00602         m_mapRespHeaders[ "EXT" ] = "";
00603 
00604         stream << SOAP_ENVELOPE_BEGIN
00605                << "<s:Fault>"
00606                << "<faultcode>"   << sWhere       << "</faultcode>"
00607                << "<faultstring>" << sFaultString << "</faultstring>";
00608     }
00609 
00610     if (sDetails.length() > 0)
00611     {
00612         stream << "<detail>" << sDetails << "</detail>";
00613     }
00614 
00615     if (m_bSOAPRequest)
00616         stream << "</s:Fault>" << SOAP_ENVELOPE_END;
00617 
00618     stream.flush();
00619 }
00620 
00622 //
00624 
00625 void HTTPRequest::FormatActionResponse( Serializer *pSer )
00626 {
00627     m_eResponseType     = ResponseTypeOther;
00628     m_sResponseTypeText = pSer->GetContentType();
00629     m_nResponseStatus   = 200;
00630 
00631     pSer->AddHeaders( m_mapRespHeaders );
00632 
00633     //m_response << pFormatter->ToString();
00634 }
00635 
00637 //
00639 
00640 void HTTPRequest::FormatActionResponse(const NameValues &args)
00641 {
00642     m_eResponseType   = ResponseTypeXML;
00643     m_nResponseStatus = 200;
00644 
00645     QTextStream stream( &m_response );
00646 
00647     stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
00648 
00649     if (m_bSOAPRequest)
00650     {
00651         m_mapRespHeaders[ "EXT" ] = "";
00652 
00653         stream << SOAP_ENVELOPE_BEGIN
00654                << "<u:" << m_sMethod << "Response xmlns:u=\""
00655                << m_sNameSpace << "\">\r\n";
00656     }
00657     else
00658         stream << "<" << m_sMethod << "Response>\r\n";
00659 
00660     NameValues::const_iterator nit = args.begin();
00661     for (; nit != args.end(); ++nit)
00662     {
00663         stream << "<" << (*nit).sName;
00664 
00665         if ((*nit).pAttributes)
00666         {
00667             NameValues::const_iterator nit2 = (*nit).pAttributes->begin();
00668             for (; nit2 != (*nit).pAttributes->end(); ++nit2)
00669             {
00670                 stream << " " << (*nit2).sName << "='"
00671                        << Encode( (*nit2).sValue ) << "'";
00672             }
00673         }
00674 
00675         stream << ">";
00676 
00677         if (m_bSOAPRequest)
00678             stream << Encode( (*nit).sValue );
00679         else
00680             stream << (*nit).sValue;
00681 
00682         stream << "</" << (*nit).sName << ">\r\n";
00683     }
00684 
00685     if (m_bSOAPRequest)
00686     {
00687         stream << "</u:" << m_sMethod << "Response>\r\n"
00688                    << SOAP_ENVELOPE_END;
00689     }
00690     else
00691         stream << "</" << m_sMethod << "Response>\r\n";
00692 
00693     stream.flush();
00694 }
00695 
00697 //
00699 
00700 void HTTPRequest::FormatRawResponse(const QString &sXML)
00701 {
00702     m_eResponseType   = ResponseTypeXML;
00703     m_nResponseStatus = 200;
00704 
00705     QTextStream stream( &m_response );
00706 
00707     stream << sXML;
00708 
00709     stream.flush();
00710 }
00712 //
00714 
00715 void HTTPRequest::FormatFileResponse( const QString &sFileName )
00716 {
00717     m_sFileName = sFileName;
00718 
00719     if (QFile::exists( m_sFileName ))
00720     {
00721 
00722         m_eResponseType                   = ResponseTypeFile;
00723         m_nResponseStatus                 = 200;
00724         m_mapRespHeaders["Cache-Control"] = "no-cache=\"Ext\", max-age = 5000";
00725     }
00726     else
00727     {
00728         m_eResponseType   = ResponseTypeHTML;
00729         m_nResponseStatus = 404;
00730         LOG(VB_UPNP, LOG_INFO,
00731             QString("HTTPRequest::FormatFileResponse(%1) - cannot find file")
00732                 .arg(sFileName));
00733     }
00734 }
00735 
00737 //
00739 
00740 void HTTPRequest::SetRequestProtocol( const QString &sLine )
00741 {
00742     m_sProtocol      = sLine.section( '/', 0, 0 ).trimmed();
00743     QString sVersion = sLine.section( '/', 1    ).trimmed();
00744 
00745     m_nMajor = sVersion.section( '.', 0, 0 ).toInt();
00746     m_nMinor = sVersion.section( '.', 1    ).toInt();
00747 }
00748 
00750 //
00752 
00753 ContentType HTTPRequest::SetContentType( const QString &sType )
00754 {
00755     if ((sType == "application/x-www-form-urlencoded"          ) ||
00756         (sType.startsWith("application/x-www-form-urlencoded;")))
00757         return( m_eContentType = ContentType_Urlencoded );
00758 
00759     if ((sType == "text/xml"                                   ) ||
00760         (sType.startsWith("text/xml;")                         ))
00761         return( m_eContentType = ContentType_XML        );
00762 
00763     return( m_eContentType = ContentType_Unknown );
00764 }
00765 
00766 
00768 //
00770 
00771 QString HTTPRequest::GetResponseStatus( void )
00772 {
00773     switch( m_nResponseStatus )
00774     {
00775         case 200:   return( "200 OK"                               );
00776         case 201:   return( "201 Created"                          );
00777         case 202:   return( "202 Accepted"                         );
00778         case 206:   return( "206 Partial Content"                  );
00779         case 304:   return( "304 Not Modified"                     );
00780         case 400:   return( "400 Bad Request"                      );
00781         case 401:   return( "401 Unauthorized"                     );
00782         case 403:   return( "403 Forbidden"                        );
00783         case 404:   return( "404 Not Found"                        );
00784         case 405:   return( "405 Method Not Allowed"               );
00785         case 406:   return( "406 Not Acceptable"                   );
00786         case 408:   return( "408 Request Timeout"                  );
00787         case 412:   return( "412 Precondition Failed"              );
00788         case 413:   return( "413 Request Entity Too Large"         );
00789         case 414:   return( "414 Request-URI Too Long"             );
00790         case 415:   return( "415 Unsupported Media Type"           );
00791         case 416:   return( "416 Requested Range Not Satisfiable"  );
00792         case 417:   return( "417 Expectation Failed"               );
00793         case 500:   return( "500 Internal Server Error"            );
00794         case 501:   return( "501 Not Implemented"                  );
00795         case 502:   return( "502 Bad Gateway"                      );
00796         case 503:   return( "503 Service Unavailable"              );
00797         case 504:   return( "504 Gateway Timeout"                  );
00798         case 505:   return( "505 HTTP Version Not Supported"       );
00799         case 510:   return( "510 Not Extended"                     );
00800     }
00801 
00802     return( QString( "%1 Unknown" ).arg( m_nResponseStatus ));
00803 }
00804 
00806 //
00808 
00809 QString HTTPRequest::GetResponseType( void )
00810 {
00811     switch( m_eResponseType )
00812     {
00813         case ResponseTypeXML    : return( "text/xml; charset=\"UTF-8\"" );
00814         case ResponseTypeHTML   : return( "text/html; charset=\"UTF-8\"" );
00815         default: break;
00816     }
00817 
00818     return( "text/plain" );
00819 }
00820 
00822 //
00824 
00825 QString HTTPRequest::GetMimeType( const QString &sFileExtension )
00826 {
00827     QString ext;
00828 
00829     for (int i = 0; i < g_nMIMELength; i++)
00830     {
00831         ext = g_MIMETypes[i].pszExtension;
00832 
00833         if ( sFileExtension.toUpper() == ext.toUpper() )
00834             return( g_MIMETypes[i].pszType );
00835     }
00836 
00837     return( "text/plain" );
00838 }
00839 
00841 //
00843 
00844 QString HTTPRequest::TestMimeType( const QString &sFileName )
00845 {
00846     QFileInfo info( sFileName );
00847     QString   sLOC    = "HTTPRequest::TestMimeType(" + sFileName + ") - ";
00848     QString   sSuffix = info.suffix().toLower();
00849     QString   sMIME   = GetMimeType( sSuffix );
00850 
00851     if ( sSuffix == "nuv" )     // If a very old recording, might be an MPEG?
00852     {
00853         // Read the header to find out:
00854         QFile file( sFileName );
00855 
00856         if ( file.open(QIODevice::ReadOnly | QIODevice::Text) )
00857         {
00858             QByteArray head = file.read(8);
00859             QString    sHex = head.toHex();
00860 
00861             LOG(VB_UPNP, LOG_DEBUG, sLOC + "file starts with " + sHex);
00862 
00863             if ( sHex == "000001ba44000400" )  // MPEG2 PS
00864                 sMIME = "video/mpeg";
00865 
00866             if ( head == "MythTVVi" )
00867             {
00868                 file.seek(100);
00869                 head = file.read(4);
00870 
00871                 if ( head == "DIVX" )
00872                 {
00873                     LOG(VB_UPNP, LOG_DEBUG, sLOC + "('MythTVVi...DIVXLAME')");
00874                     sMIME = "video/mp4";
00875                 }
00876                 // NuppelVideo is "RJPG" at byte 612
00877                 // We could also check the audio (LAME or RAWA),
00878                 // but since most UPnP clients choke on Nuppel, no need
00879             }
00880 
00881             file.close();
00882         }
00883         else
00884             LOG(VB_GENERAL, LOG_ERR, sLOC + "Could not read file");
00885     }
00886 
00887     LOG(VB_UPNP, LOG_INFO, sLOC + "type is " + sMIME);
00888     return sMIME;
00889 }
00890 
00892 //
00894 
00895 long HTTPRequest::GetParameters( QString sParams, QStringMap &mapParams  )
00896 {
00897     long nCount = 0;
00898 
00899     LOG(VB_UPNP, LOG_DEBUG, QString("sParams: '%1'").arg(sParams));
00900 
00901     // This looks odd, but it is here to cope with stupid UPnP clients that
00902     // forget to de-escape the URLs.  We can't map %26 here as well, as that
00903     // breaks anything that is trying to pass & as part of a name or value.
00904     sParams.replace( "&amp;", "&" );
00905 
00906     if (sParams.length() > 0)
00907     {
00908         QStringList params = sParams.split('&', QString::SkipEmptyParts);
00909 
00910         for ( QStringList::Iterator it  = params.begin();
00911                                     it != params.end();  ++it )
00912         {
00913             QString sName  = (*it).section( '=', 0, 0 );
00914             QString sValue = (*it).section( '=', 1 );
00915             sValue.replace("+"," ");
00916 
00917             if ((sName.length() != 0) && (sValue.length() !=0))
00918             {
00919                 sName  = QUrl::fromPercentEncoding(sName.toUtf8());
00920                 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
00921 
00922                 mapParams.insert( sName.trimmed(), sValue );
00923                 nCount++;
00924             }
00925         }
00926     }
00927 
00928     return nCount;
00929 }
00930 
00931 
00933 //
00935 
00936 QString HTTPRequest::GetHeaderValue( const QString &sKey, QString sDefault )
00937 {
00938     QStringMap::iterator it = m_mapHeaders.find( sKey.toLower() );
00939 
00940     if ( it == m_mapHeaders.end())
00941         return( sDefault );
00942 
00943     return *it;
00944 }
00945 
00946 
00948 //
00950 
00951 QString HTTPRequest::GetAdditionalHeaders( void )
00952 {
00953     QString sHeader = m_szServerHeaders;
00954 
00955     // Override the cache-control header on protected resources.
00956 
00957     if (m_bProtected)
00958         m_mapRespHeaders[ "Cache-control" ] = "no-cache";
00959 
00960     for ( QStringMap::iterator it  = m_mapRespHeaders.begin();
00961                                it != m_mapRespHeaders.end();
00962                              ++it )
00963     {
00964         sHeader += it.key()  + ": ";
00965         sHeader += *it + "\r\n";
00966     }
00967 
00968     return( sHeader );
00969 }
00970 
00972 //
00974 
00975 bool HTTPRequest::GetKeepAlive()
00976 {
00977     bool bKeepAlive = true;
00978 
00979     // if HTTP/1.0... must default to false
00980 
00981     if ((m_nMajor == 1) && (m_nMinor == 0))
00982         bKeepAlive = false;
00983 
00984     // Read Connection Header...
00985 
00986     QString sConnection = GetHeaderValue( "connection", "default" ).toLower();
00987 
00988     if ( sConnection == "close" )
00989         bKeepAlive = false;
00990     else if (sConnection == "keep-alive")
00991         bKeepAlive = true;
00992 
00993    return bKeepAlive;
00994 }
00995 
00997 //
00999 
01000 bool HTTPRequest::ParseRequest()
01001 {
01002     bool bSuccess = false;
01003 
01004     try
01005     {
01006         // Read first line to determin requestType
01007         QString sRequestLine = ReadLine( 2000 );
01008 
01009         if ( sRequestLine.length() == 0)
01010         {
01011             LOG(VB_GENERAL, LOG_ERR, "Timeout reading first line of request." );
01012             return false;
01013         }
01014 
01015         // -=>TODO: Should read lines until a valid request???
01016 
01017         ProcessRequestLine( sRequestLine );
01018 
01019         // Make sure there are a few default values
01020 
01021         m_mapHeaders[ "content-length" ] = "0";
01022         m_mapHeaders[ "content-type"   ] = "unknown";
01023 
01024         // Read Header
01025 
01026         bool    bDone = false;
01027         QString sLine = ReadLine( 2000 );
01028 
01029         while (( sLine.length() > 0 ) && !bDone )
01030         {
01031             if (sLine != "\r\n")
01032             {
01033                 QString sName  = sLine.section( ':', 0, 0 ).trimmed();
01034                 QString sValue = sLine.section( ':', 1 );
01035 
01036                 sValue.truncate( sValue.length() - 2 );
01037 
01038                 if ((sName.length() != 0) && (sValue.length() !=0))
01039                 {
01040                     m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
01041 
01042                     if (sName.contains( "dlna", Qt::CaseInsensitive ))
01043                     {
01044                         LOG(VB_UPNP, LOG_INFO,
01045                            QString( "HTTPRequest::ParseRequest - Header: %1:%2")
01046                                .arg(sName) .arg(sValue));
01047                     }
01048                 }
01049 
01050                 sLine = ReadLine( 2000 );
01051             }
01052             else
01053                 bDone = true;
01054         }
01055 
01056         // Check to see if we found the end of the header or we timed out.
01057 
01058         if (!bDone)
01059         {
01060             LOG(VB_GENERAL, LOG_INFO, "Timeout waiting for request header." );
01061             return false;
01062         }
01063 
01064         m_bProtected = false;
01065 
01066         if (IsUrlProtected( m_sBaseUrl ))
01067         {
01068             if (!Authenticated())
01069             {
01070                 m_eResponseType   = ResponseTypeHTML;
01071                 m_nResponseStatus = 401;
01072                 m_mapRespHeaders["WWW-Authenticate"] = "Basic realm=\"MythTV\"";
01073 
01074                 m_response.write( Static401Error );
01075 
01076                 return true;
01077             }
01078 
01079             m_bProtected = true;
01080         }
01081 
01082         bSuccess = true;
01083 
01084         SetContentType( m_mapHeaders[ "content-type" ] );
01085 
01086         // Lets load payload if any.
01087         long nPayloadSize = m_mapHeaders[ "content-length" ].toLong();
01088 
01089         if (nPayloadSize > 0)
01090         {
01091             char *pszPayload = new char[ nPayloadSize + 2 ];
01092             long  nBytes     = 0;
01093 
01094             nBytes = ReadBlock( pszPayload, nPayloadSize, 5000 );
01095             if (nBytes == nPayloadSize )
01096             {
01097                 m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
01098 
01099                 // See if the payload is just data from a form post
01100                 if (m_eContentType == ContentType_Urlencoded)
01101                     GetParameters( m_sPayload, m_mapParams );
01102             }
01103             else
01104             {
01105                 LOG(VB_GENERAL, LOG_ERR,
01106                   QString("Unable to read entire payload (read %1 of %2 bytes)")
01107                       .arg( nBytes ) .arg( nPayloadSize ) );
01108                 bSuccess = false;
01109             }
01110 
01111             delete [] pszPayload;
01112         }
01113 
01114         // Check to see if this is a SOAP encoded message
01115 
01116         QString sSOAPAction = GetHeaderValue( "SOAPACTION", "" );
01117 
01118         if (sSOAPAction.length() > 0)
01119             bSuccess = ProcessSOAPPayload( sSOAPAction );
01120         else
01121             ExtractMethodFromURL();
01122 
01123 #if 0
01124         if (m_sMethod != "*" )
01125             LOG(VB_UPNP, LOG_DEBUG,
01126                 QString("HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
01127                         "Method (%3) - Bytes in Socket Buffer (%4)")
01128                     .arg(getSocketHandle()) .arg(m_sBaseUrl)
01129                     .arg(m_sMethod) .arg(BytesAvailable()));
01130 #endif
01131     }
01132     catch(...)
01133     {
01134         LOG(VB_GENERAL, LOG_WARNING,
01135             "Unexpected exception in HTTPRequest::ParseRequest" );
01136     }
01137 
01138     return bSuccess;
01139 }
01140 
01142 //
01144 
01145 void HTTPRequest::ProcessRequestLine( const QString &sLine )
01146 {
01147     m_sRawRequest = sLine;
01148 
01149     QString     sToken;
01150     QStringList tokens = sLine.split(m_procReqLineExp, QString::SkipEmptyParts);
01151     int         nCount = tokens.count();
01152 
01153     // ----------------------------------------------------------------------
01154 
01155     if ( sLine.startsWith( QString("HTTP/") ))
01156         m_eType = RequestTypeResponse;
01157     else
01158         m_eType = RequestTypeUnknown;
01159 
01160     // ----------------------------------------------------------------------
01161     // if this is actually a response, then sLine's format will be:
01162     //      HTTP/m.n <response code> <response text>
01163     // otherwise:
01164     //      <method> <Resource URI> HTTP/m.n
01165     // ----------------------------------------------------------------------
01166 
01167     if (m_eType != RequestTypeResponse)
01168     {
01169         // ------------------------------------------------------------------
01170         // Process as a request
01171         // ------------------------------------------------------------------
01172 
01173         if (nCount > 0)
01174             SetRequestType( tokens[0].trimmed() );
01175 
01176         if (nCount > 1)
01177         {
01178             //m_sBaseUrl = tokens[1].section( '?', 0, 0).trimmed();
01179             m_sBaseUrl = (QUrl::fromPercentEncoding(tokens[1].toUtf8()))
01180                               .section( '?', 0, 0).trimmed();
01181 
01182             m_sResourceUrl = m_sBaseUrl; // Save complete url without parameters
01183 
01184             // Process any Query String Parameters
01185             QString sQueryStr = (QUrl::fromPercentEncoding(tokens[1].toUtf8()))
01186                                      .section( '?', 1, 1 );
01187 
01188             if (sQueryStr.length() > 0)
01189                 GetParameters( sQueryStr, m_mapParams );
01190         }
01191 
01192         if (nCount > 2)
01193             SetRequestProtocol( tokens[2].trimmed() );
01194     }
01195     else
01196     {
01197         // ------------------------------------------------------------------
01198         // Process as a Response
01199         // ------------------------------------------------------------------
01200         if (nCount > 0)
01201             SetRequestProtocol( tokens[0].trimmed() );
01202 
01203         if (nCount > 1)
01204             m_nResponseStatus = tokens[1].toInt();
01205     }
01206 }
01207 
01209 //
01211 
01212 bool HTTPRequest::ParseRange( QString sRange,
01213                               long long   llSize,
01214                               long long *pllStart,
01215                               long long *pllEnd   )
01216 {
01217     // ----------------------------------------------------------------------
01218     // -=>TODO: Only handle 1 range at this time... 
01219     //          should make work with full spec.
01220     // ----------------------------------------------------------------------
01221 
01222     if (sRange.length() == 0)
01223         return false;
01224 
01225     // ----------------------------------------------------------------------
01226     // remove any "bytes="
01227     // ----------------------------------------------------------------------
01228     int nIdx = sRange.indexOf(m_parseRangeExp);
01229 
01230     if (nIdx < 0)
01231         return false;
01232 
01233     if (nIdx > 0)
01234         sRange.remove( 0, nIdx );
01235 
01236     // ----------------------------------------------------------------------
01237     // Split multiple ranges
01238     // ----------------------------------------------------------------------
01239 
01240     QStringList ranges = sRange.split(',', QString::SkipEmptyParts);
01241 
01242     if (ranges.count() == 0)
01243         return false;
01244 
01245     // ----------------------------------------------------------------------
01246     // Split first range into its components
01247     // ----------------------------------------------------------------------
01248 
01249     QStringList parts = ranges[0].split('-');
01250 
01251     if (parts.count() != 2)
01252         return false;
01253 
01254     if (parts[0].isNull() && parts[1].isNull())
01255         return false;
01256 
01257     // ----------------------------------------------------------------------
01258     //
01259     // ----------------------------------------------------------------------
01260 
01261     bool conv_ok;
01262     if (parts[0].isNull())
01263     {
01264         // ------------------------------------------------------------------
01265         // Does it match "-####"
01266         // ------------------------------------------------------------------
01267 
01268         long long llValue = parts[1].toLongLong(&conv_ok);
01269         if (!conv_ok)    return false;
01270 
01271         *pllStart = llSize - llValue;
01272         *pllEnd   = llSize - 1;
01273     }
01274     else if (parts[1].isNull())
01275     {
01276         // ------------------------------------------------------------------
01277         // Does it match "####-"
01278         // ------------------------------------------------------------------
01279 
01280         *pllStart = parts[0].toLongLong(&conv_ok);
01281 
01282         if (!conv_ok)
01283             return false;
01284 
01285         *pllEnd   = llSize - 1;
01286     }
01287     else
01288     {
01289         // ------------------------------------------------------------------
01290         // Must be  "####-####"
01291         // ------------------------------------------------------------------
01292 
01293         *pllStart = parts[0].toLongLong(&conv_ok);
01294         if (!conv_ok)    return false;
01295         *pllEnd   = parts[1].toLongLong(&conv_ok);
01296         if (!conv_ok)    return false;
01297 
01298         if (*pllStart > *pllEnd)
01299             return false;
01300     }
01301 
01302 #if 0
01303     LOG(VB_GENERAL, LOG_DEBUG, QString("%1 Range Requested %2 - %3")
01304         .arg(getSocketHandle()) .arg(*pllStart) .arg(*pllEnd));
01305 #endif
01306 
01307     return true;
01308 }
01309 
01311 //
01313 
01314 void HTTPRequest::ExtractMethodFromURL()
01315 {
01316     // Strip out leading http://192.168.1.1:6544/ -> /
01317     // Should fix #8678
01318     QRegExp sRegex("^http://.*/");
01319     sRegex.setMinimal(true);
01320     m_sBaseUrl.replace(sRegex, "/");
01321 
01322     QStringList sList = m_sBaseUrl.split('/', QString::SkipEmptyParts);
01323 
01324     m_sMethod = "";
01325 
01326     if (sList.size() > 0)
01327     {
01328         m_sMethod = sList.last();
01329         sList.pop_back();
01330     }
01331 
01332     m_sBaseUrl = '/' + sList.join( "/" );
01333     LOG(VB_UPNP, LOG_INFO, QString("ExtractMethodFromURL(end) : %1 : %2")
01334                                .arg(m_sMethod).arg(m_sBaseUrl));
01335 }
01336 
01338 //
01340 
01341 bool HTTPRequest::ProcessSOAPPayload( const QString &sSOAPAction )
01342 {
01343     bool bSuccess = false;
01344 
01345     // ----------------------------------------------------------------------
01346     // Open Supplied XML uPnp Description file.
01347     // ----------------------------------------------------------------------
01348 
01349     LOG(VB_UPNP, LOG_DEBUG,
01350         QString("HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
01351     QDomDocument doc ( "request" );
01352 
01353     QString sErrMsg;
01354     int     nErrLine = 0;
01355     int     nErrCol  = 0;
01356 
01357     if (!doc.setContent( m_sPayload, true, &sErrMsg, &nErrLine, &nErrCol ))
01358     {
01359         LOG(VB_GENERAL, LOG_ERR,
01360             QString( "Error parsing request at line: %1 column: %2 : %3" )
01361                 .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
01362         return( false );
01363     }
01364 
01365     // --------------------------------------------------------------
01366     // XML Document Loaded... now parse it
01367     // --------------------------------------------------------------
01368 
01369     QString sService;
01370 
01371     if (sSOAPAction.contains( '#' ))
01372     {
01373         m_sNameSpace    = sSOAPAction.section( '#', 0, 0).remove( 0, 1);
01374         m_sMethod       = sSOAPAction.section( '#', 1 );
01375         m_sMethod.remove( m_sMethod.length()-1, 1 );
01376     }
01377     else
01378     {
01379         if (sSOAPAction.contains( '/' ))
01380         {
01381             int nPos      = sSOAPAction.lastIndexOf( '/' );
01382             m_sNameSpace  = sSOAPAction.mid(1, nPos);
01383             m_sMethod     = sSOAPAction.mid(nPos + 1,
01384                                             sSOAPAction.length() - nPos - 2);
01385 
01386             nPos          = m_sNameSpace.lastIndexOf( '/', -2);
01387             sService      = m_sNameSpace.mid(nPos + 1,
01388                                              m_sNameSpace.length() - nPos - 2);
01389             m_sNameSpace  = m_sNameSpace.mid( 0, nPos );
01390         }
01391         else
01392         {
01393             m_sNameSpace = QString::null;
01394             m_sMethod    = sSOAPAction;
01395             m_sMethod.remove( QChar( '\"' ) );
01396         }
01397     }
01398 
01399     QDomNodeList oNodeList = doc.elementsByTagNameNS( m_sNameSpace, m_sMethod );
01400 
01401     if (oNodeList.count() == 0)
01402         oNodeList = 
01403             doc.elementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/",
01404                                     "Body");
01405 
01406     if (oNodeList.count() > 0)
01407     {
01408         QDomNode oMethod = oNodeList.item(0);
01409 
01410         if (!oMethod.isNull())
01411         {
01412             m_bSOAPRequest = true;
01413 
01414             for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
01415                            oNode = oNode.nextSibling() )
01416             {
01417                 QDomElement e = oNode.toElement();
01418 
01419                 if (!e.isNull())
01420                 {
01421                     QString sName  = e.tagName();
01422                     QString sValue = "";
01423 
01424                     QDomText  oText = oNode.firstChild().toText();
01425 
01426                     if (!oText.isNull())
01427                         sValue = oText.nodeValue();
01428 
01429                     sName  = QUrl::fromPercentEncoding(sName.toUtf8());
01430                     sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
01431 
01432                     m_mapParams.insert( sName.trimmed(), sValue );
01433                 }
01434             }
01435 
01436             bSuccess = true;
01437         }
01438     }
01439 
01440     return bSuccess;
01441 }
01442 
01444 //
01446 
01447 Serializer *HTTPRequest::GetSerializer()
01448 {
01449     Serializer *pSerializer = NULL;
01450 
01451     if (m_bSOAPRequest) 
01452         pSerializer = (Serializer *)new SoapSerializer(&m_response,
01453                                                        m_sNameSpace, m_sMethod);
01454     else
01455     {
01456         QString sAccept = GetHeaderValue( "Accept", "*/*" );
01457         
01458         if (sAccept.contains( "application/json", Qt::CaseInsensitive ))    
01459             pSerializer = (Serializer *)new JSONSerializer(&m_response,
01460                                                            m_sMethod);
01461         else if (sAccept.contains( "text/javascript", Qt::CaseInsensitive ))    
01462             pSerializer = (Serializer *)new JSONSerializer(&m_response,
01463                                                            m_sMethod);
01464         else if (sAccept.contains( "text/x-apple-plist+xml", Qt::CaseInsensitive ))
01465             pSerializer = (Serializer *)new XmlPListSerializer(&m_response);
01466     }
01467 
01468     // Default to XML
01469 
01470     if (pSerializer == NULL)
01471         pSerializer = (Serializer *)new XmlSerializer(&m_response, m_sMethod);
01472 
01473     return pSerializer;
01474 }
01475 
01477 //
01479 
01480 QString HTTPRequest::Encode(const QString &sIn)
01481 {
01482     QString sStr = sIn;
01483 #if 0
01484     LOG(VB_UPNP, LOG_DEBUG, 
01485         QString("HTTPRequest::Encode Input : %1").arg(sStr));
01486 #endif
01487     sStr.replace('&', "&amp;" ); // This _must_ come first
01488     sStr.replace('<', "&lt;"  );
01489     sStr.replace('>', "&gt;"  );
01490     sStr.replace('"', "&quot;");
01491     sStr.replace("'", "&apos;");
01492 
01493 #if 0
01494     LOG(VB_UPNP, LOG_DEBUG,
01495         QString("HTTPRequest::Encode Output : %1").arg(sStr));
01496 #endif
01497     return sStr;
01498 }
01499 
01501 //
01503 
01504 QString HTTPRequest::GetETagHash(const QByteArray &data)
01505 {
01506     QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
01507 
01508     return ("\"" + hash.toHex() + "\"");
01509 }
01510 
01512 //
01514 
01515 bool HTTPRequest::IsUrlProtected( const QString &sBaseUrl )
01516 {
01517     QString sProtected = UPnp::GetConfiguration()->GetValue( "HTTP/Protected/Urls", "/setup;/Config" );
01518 
01519     QStringList oList = sProtected.split( ';' );
01520 
01521     for( int nIdx = 0; nIdx < oList.count(); nIdx++)
01522     {
01523         if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
01524             return true;
01525     }
01526 
01527     return false;
01528 }
01529 
01531 //
01533 
01534 bool HTTPRequest::Authenticated()
01535 {
01536     QStringList oList = m_mapHeaders[ "authorization" ].split( ' ' );
01537 
01538     if (oList.count() < 2)
01539         return false;
01540 
01541     if (oList[0].compare( "basic", Qt::CaseInsensitive ) != 0)
01542         return false;
01543 
01544     QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
01545     
01546     oList = sCredentials.split( ':' );
01547 
01548     if (oList.count() < 2)
01549         return false;
01550 
01551     QString sUserName = UPnp::GetConfiguration()->GetValue( "HTTP/Protected/UserName", "admin" );
01552     
01553 
01554     if (oList[0].compare( sUserName, Qt::CaseInsensitive ) != 0)
01555         return false;
01556 
01557     QString sPassword = UPnp::GetConfiguration()->GetValue( "HTTP/Protected/Password", 
01558                                  /* mythtv */ "8hDRxR1+E/n3/s3YUOhF+lUw7n4=" );
01559 
01560     QCryptographicHash crypto( QCryptographicHash::Sha1 );
01561 
01562     crypto.addData( oList[1].toUtf8() );
01563 
01564     QString sPasswordHash( crypto.result().toBase64() );
01565 
01566     if (sPasswordHash != sPassword )
01567         return false;
01568     
01569     return true;
01570 }
01571 
01572 
01575 //
01576 // BufferedSocketDeviceRequest Class Implementation
01577 //
01580 
01581 BufferedSocketDeviceRequest::BufferedSocketDeviceRequest( BufferedSocketDevice *pSocket )
01582 {
01583     m_pSocket  = pSocket;
01584 }
01585 
01587 //
01589 
01590 qlonglong BufferedSocketDeviceRequest::BytesAvailable(void)
01591 {
01592     if (m_pSocket)
01593         return( m_pSocket->BytesAvailable() );
01594 
01595     return( 0 );
01596 }
01597 
01599 //
01601 
01602 qulonglong BufferedSocketDeviceRequest::WaitForMore( int msecs, bool *timeout )
01603 {
01604     if (m_pSocket)
01605         return( m_pSocket->WaitForMore( msecs, timeout ));
01606 
01607     return( 0 );
01608 }
01609 
01611 //
01613 
01614 bool BufferedSocketDeviceRequest::CanReadLine()
01615 {
01616     if (m_pSocket)
01617         return( m_pSocket->CanReadLine() );
01618 
01619     return( false );
01620 }
01621 
01623 //
01625 
01626 QString BufferedSocketDeviceRequest::ReadLine( int msecs )
01627 {
01628     QString sLine;
01629 
01630     if (m_pSocket)
01631         sLine = m_pSocket->ReadLine( msecs );
01632 
01633     return( sLine );
01634 }
01635 
01637 //
01639 
01640 qlonglong BufferedSocketDeviceRequest::ReadBlock(
01641     char *pData, qulonglong nMaxLen, int msecs)
01642 {
01643     if (m_pSocket)
01644     {
01645         if (msecs == 0)
01646             return( m_pSocket->ReadBlock( pData, nMaxLen ));
01647         else
01648         {
01649             bool bTimeout = false;
01650 
01651             while ( (BytesAvailable() < (int)nMaxLen) && !bTimeout )
01652                 m_pSocket->WaitForMore( msecs, &bTimeout );
01653 
01654             // Just return what we have even if timed out.
01655 
01656             return( m_pSocket->ReadBlock( pData, nMaxLen ));
01657         }
01658     }
01659 
01660     return( -1 );
01661 }
01662 
01664 //
01666 
01667 qlonglong BufferedSocketDeviceRequest::WriteBlock(
01668     const char *pData, qulonglong nLen)
01669 {
01670     if (m_pSocket)
01671         return( m_pSocket->WriteBlock( pData, nLen ));
01672 
01673     return( -1 );
01674 }
01675 
01677 //
01679 
01680 qlonglong BufferedSocketDeviceRequest::WriteBlockDirect(
01681     const char *pData, qulonglong nLen)
01682 {
01683     if (m_pSocket)
01684         return( m_pSocket->WriteBlockDirect( pData, nLen ));
01685 
01686     return( -1 );
01687 }
01688 
01690 //
01692 
01693 QString BufferedSocketDeviceRequest::GetHostAddress()
01694 {
01695     return( m_pSocket->SocketDevice()->address().toString() );
01696 }
01697 
01699 //
01701 
01702 QString BufferedSocketDeviceRequest::GetPeerAddress()
01703 {
01704     return( m_pSocket->SocketDevice()->peerAddress().toString() );
01705 }
01706 
01708 //
01710 
01711 void BufferedSocketDeviceRequest::SetBlocking( bool bBlock )
01712 {
01713     if (m_pSocket)
01714         return( m_pSocket->SocketDevice()->setBlocking( bBlock ));
01715 }
01716 
01718 //
01720 
01721 bool BufferedSocketDeviceRequest::IsBlocking()
01722 {
01723     if (m_pSocket)
01724         return( m_pSocket->SocketDevice()->blocking());
01725 
01726     return false;
01727 }
01728 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends