• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KIOSlave

parsinghelpers.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002     Copyright (C) 2008 Andreas Hartmetz <ahartmetz@gmail.com>
00003 
00004     This library is free software; you can redistribute it and/or
00005     modify it under the terms of the GNU Library General Public
00006     License as published by the Free Software Foundation; either
00007     version 2 of the License, or (at your option) any later version.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to
00016     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017     Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include <QDir>
00021 #include <QMap>
00022 #include <QTextCodec>
00023 #include <QUrl>
00024 
00025 #include <kcodecs.h>
00026 
00027 // Advance *pos beyond spaces / tabs
00028 static void skipSpace(const char input[], int *pos, int end)
00029 {
00030     int idx = *pos;
00031     while (idx < end && (input[idx] == ' ' || input[idx] == '\t')) {
00032         idx++;
00033     }
00034     *pos = idx;
00035     return;
00036 }
00037 
00038 // Advance *pos to start of next line while being forgiving about line endings.
00039 // Return false if the end of the header has been reached, true otherwise.
00040 static bool nextLine(const char input[], int *pos, int end)
00041 {
00042     int idx = *pos;
00043     while (idx < end && input[idx] != '\r' && input[idx] != '\n') {
00044         idx++;
00045     }
00046     int rCount = 0;
00047     int nCount = 0;
00048     while (idx < end && qMax(rCount, nCount) < 2 && (input[idx] == '\r' || input[idx] == '\n')) {
00049         input[idx] == '\r' ? rCount++ : nCount++;
00050         idx++;
00051     }
00052     if (idx < end && qMax(rCount, nCount) == 2 && qMin(rCount, nCount) == 1) {
00053         // if just one of the others is missing eat it too.
00054         // this ensures that conforming headers using the proper
00055         // \r\n sequence (and also \n\r) will be parsed correctly.
00056         if ((rCount == 1 && input[idx] == '\r') || (nCount == 1 && input[idx] == '\n')) {
00057             idx++;
00058         }
00059     }
00060 
00061     *pos = idx;
00062     return idx < end && rCount < 2 && nCount < 2;
00063 }
00064 
00065 //Return true if the term was found, false otherwise. Advance *pos.
00066 //If (*pos + strlen(term) >= end) just advance *pos to end and return false.
00067 //This means that users should always search for the shortest terms first.
00068 static bool consume(const char input[], int *pos, int end, const char *term)
00069 {
00070     // note: gcc/g++ is quite good at optimizing away redundant strlen()s
00071     int idx = *pos;
00072     if (idx + (int)strlen(term) >= end) {
00073         *pos = end;
00074         return false;
00075     }
00076     if (strncasecmp(&input[idx], term, strlen(term)) == 0) {
00077         *pos = idx + strlen(term);
00078         return true;
00079     }
00080     return false;
00081 }
00082 
00083 
00084 QByteArray TokenIterator::next()
00085 {
00086     QPair<int, int> token = m_tokens[m_currentToken++];
00087     //fromRawData brings some speed advantage but also the requirement to keep the text buffer
00088     //around. this together with implicit sharing (you don't know where copies end up)
00089     //is dangerous!
00090     //return QByteArray::fromRawData(&m_buffer[token.first], token.second - token.first);
00091     return QByteArray(&m_buffer[token.first], token.second - token.first);
00092 }
00093 
00094 QByteArray TokenIterator::current() const
00095 {
00096     QPair<int, int> token = m_tokens[m_currentToken - 1];
00097     //return QByteArray::fromRawData(&m_buffer[token.first], token.second - token.first);
00098     return QByteArray(&m_buffer[token.first], token.second - token.first);
00099 }
00100 
00101 QList<QByteArray> TokenIterator::all() const
00102 {
00103     QList<QByteArray> ret;
00104     for (int i = 0; i < m_tokens.count(); i++) {
00105         QPair<int, int> token = m_tokens[i];
00106         ret.append(QByteArray(&m_buffer[token.first], token.second - token.first));
00107     }
00108     return ret;
00109 }
00110 
00111 
00112 HeaderTokenizer::HeaderTokenizer(char *buffer)
00113  : m_buffer(buffer)
00114 {
00115     // add information about available headers and whether they have one or multiple,
00116     // comma-separated values.
00117 
00118     //The following response header fields are from RFC 2616 unless otherwise specified.
00119     //Hint: search the web for e.g. 'http "accept-ranges header"' to find information about
00120     //a header field.
00121     static const HeaderFieldTemplate headerFieldTemplates[] = {
00122         {"accept-ranges", false},
00123         {"age", false},
00124         {"cache-control", true},
00125         {"connection", true},
00126         {"content-disposition", false}, //is multi-valued in a way, but with ";" separator!
00127         {"content-encoding", true},
00128         {"content-language", true},
00129         {"content-length", false},
00130         {"content-location", false},
00131         {"content-md5", false},
00132         {"content-type", false},
00133         {"date", false},
00134         {"dav", true}, //RFC 2518
00135         {"etag", false},
00136         {"expires", false},
00137         {"keep-alive", false}, //RFC 2068
00138         {"last-modified", false},
00139         {"link", false}, //RFC 2068, multi-valued with ";" separator
00140         {"location", false},
00141         {"p3p", true}, // http://www.w3.org/TR/P3P/
00142         {"pragma", true},
00143         {"proxy-authenticate", false}, //complicated multi-valuedness: quoted commas don't separate
00144                                        //multiple values. we handle this at a higher level.
00145         {"proxy-connection", true}, //inofficial but well-known; to avoid misunderstandings
00146                                     //when using "connection" when talking to a proxy.
00147         {"refresh", false}, //not sure, only found some mailing list posts mentioning it
00148         {"set-cookie", false}, //RFC 2109; the multi-valuedness seems to be usually achieved
00149                                //by sending several instances of this field as opposed to
00150                                //usually comma-separated lists with maybe multiple instances.
00151         {"transfer-encoding", true},
00152         {"upgrade", true},
00153         {"warning", true},
00154         {"www-authenticate", false} //see proxy-authenticate
00155     };
00156 
00157     for (uint i = 0; i < sizeof(headerFieldTemplates) / sizeof(HeaderFieldTemplate); i++) {
00158         const HeaderFieldTemplate &ft = headerFieldTemplates[i];
00159         insert(QByteArray(ft.name), HeaderField(ft.isMultiValued));
00160     }
00161 }
00162 
00163 int HeaderTokenizer::tokenize(int begin, int end)
00164 {
00165     char *buf = m_buffer;  //keep line length in check :/
00166     int idx = begin;
00167     int startIdx = begin; //multi-purpose start of current token
00168     bool multiValuedEndedWithComma = false; //did the last multi-valued line end with a comma?
00169     QByteArray headerKey;
00170     do {
00171 
00172         if (buf[idx] == ' ' || buf [idx] == '\t') {
00173             // line continuation; preserve startIdx except (see below)
00174             if (headerKey.isEmpty()) {
00175                 continue;
00176             }
00177             // turn CR/LF into spaces for later parsing convenience
00178             int backIdx = idx - 1;
00179             while (backIdx >= begin && (buf[backIdx] == '\r' || buf[backIdx] == '\n')) {
00180                 buf[backIdx--] = ' ';
00181             }
00182 
00183             // multiple values, comma-separated: add new value or continue previous?
00184             if (operator[](headerKey).isMultiValued) {
00185                 if (multiValuedEndedWithComma) {
00186                     // start new value; this is almost like no line continuation
00187                     skipSpace(buf, &idx, end);
00188                     startIdx = idx;
00189                 } else {
00190                     // continue previous value; this is tricky. unit tests to the rescue!
00191                     if (operator[](headerKey).beginEnd.last().first == startIdx) {
00192                         // remove entry, it will be re-added because already idx != startIdx
00193                         operator[](headerKey).beginEnd.removeLast();
00194                     } else {
00195                         // no comma, no entry: the prev line was whitespace only - start new value
00196                         skipSpace(buf, &idx, end);
00197                         startIdx = idx;
00198                     }
00199                 }
00200             }
00201 
00202         } else {
00203             // new field
00204             startIdx = idx;
00205             // also make sure that there is at least one char after the colon
00206             while (idx < (end - 1) && buf[idx] != ':' && buf[idx] != '\r' && buf[idx] != '\n') {
00207                 buf[idx] = tolower(buf[idx]);
00208                 idx++;
00209             }
00210             if (buf[idx] != ':') {
00211                 //malformed line: no colon
00212                 headerKey.clear();
00213                 continue;
00214             }
00215             headerKey = QByteArray(&buf[startIdx], idx - startIdx);
00216             if (!contains(headerKey)) {
00217                 //we don't recognize this header line
00218                 headerKey.clear();
00219                 continue;
00220             }
00221             // skip colon & leading whitespace
00222             idx++;
00223             skipSpace(buf, &idx, end);
00224             startIdx = idx;
00225         }
00226 
00227         // we have the name/key of the field, now parse the value
00228         if (!operator[](headerKey).isMultiValued) {
00229 
00230             // scan to end of line
00231             while (idx < end && buf[idx] != '\r' && buf[idx] != '\n') {
00232                 idx++;
00233             }
00234             if (!operator[](headerKey).beginEnd.isEmpty()) {
00235                 // there already is an entry; are we just in a line continuation?
00236                 if (operator[](headerKey).beginEnd.last().first == startIdx) {
00237                     // line continuation: delete previous entry and later insert a new, longer one.
00238                     operator[](headerKey).beginEnd.removeLast();
00239                 }
00240             }
00241             operator[](headerKey).beginEnd.append(QPair<int, int>(startIdx, idx));
00242 
00243         } else {
00244 
00245             // comma-separated list
00246             while (true) {
00247                 //skip one value
00248                 while (idx < end && buf[idx] != '\r' && buf[idx] != '\n' && buf[idx] != ',') {
00249                     idx++;
00250                 }
00251                 if (idx != startIdx) {
00252                     operator[](headerKey).beginEnd.append(QPair<int, int>(startIdx, idx));
00253                 }
00254                 multiValuedEndedWithComma = buf[idx] == ',';
00255                 //skip comma(s) and leading whitespace, if any respectively
00256                 while (idx < end && buf[idx] == ',') {
00257                     idx++;
00258                 }
00259                 skipSpace(buf, &idx, end);
00260                 //next value or end-of-line / end of header?
00261                 if (buf[idx] >= end || buf[idx] == '\r' || buf[idx] == '\n') {
00262                     break;
00263                 }
00264                 //next value
00265                 startIdx = idx;
00266             }
00267         }
00268     } while (nextLine(buf, &idx, end));
00269     return idx;
00270 }
00271 
00272 
00273 TokenIterator HeaderTokenizer::iterator(const char *key) const
00274 {
00275     QByteArray keyBa = QByteArray::fromRawData(key, strlen(key));
00276     if (contains(keyBa)) {
00277         return TokenIterator(value(keyBa).beginEnd, m_buffer);
00278     } else {
00279         return TokenIterator(m_nullTokens, m_buffer);
00280     }
00281 }
00282 
00283 static void skipLWS(const QString &str, int &pos)
00284 {
00285     while (pos < str.length() && (str[pos] == QLatin1Char(' ') || str[pos] == QLatin1Char('\t')))
00286         ++pos;
00287 }
00288 
00289 // keep the common ending, this allows the compiler to join them
00290 static const char typeSpecials[] =  "{}*'%()<>@,;:\\\"/[]?=";
00291 static const char attrSpecials[] =     "'%()<>@,;:\\\"/[]?=";
00292 static const char valueSpecials[] =      "()<>@,;:\\\"/[]?=";
00293 
00294 static bool specialChar(const QChar &ch, const char *specials)
00295 {
00296     if( (ch.unicode() < 32) || (ch.unicode() >= 128) )
00297         return true;
00298 
00299     for( int i = strlen(specials) - 1; i>= 0; i--)
00300        if( ch == QLatin1Char(specials[i]) )
00301            return true;
00302 
00303     return false;
00304 }
00305 
00321 static QString extractUntil(const QString &str, QChar term, int &pos, const char *specials)
00322 {
00323     QString out;
00324     skipLWS(str, pos);
00325     bool valid = true;
00326 
00327     while (pos < str.length() && (str[pos] != term)) {
00328         out += str[pos];
00329         valid &= !specialChar(str[pos], specials);
00330         ++pos;
00331     }
00332 
00333     if (pos < str.length()) // Stopped due to finding term
00334         ++pos;
00335 
00336     if( !valid )
00337         return QString();
00338 
00339     // Remove trailing linear whitespace...
00340     while (out.endsWith(QLatin1Char(' ')) || out.endsWith(QLatin1Char('\t')))
00341         out.chop(1);
00342 
00343     if( out.contains(QLatin1Char(' ')) )
00344         out.clear();
00345 
00346     return out;
00347 }
00348 
00349 // As above, but also handles quotes..
00350 // pos is set to -1 on parse error
00351 static QString extractMaybeQuotedUntil(const QString &str, int &pos)
00352 {
00353     const QChar term = QLatin1Char(';');
00354 
00355     skipLWS(str, pos);
00356 
00357     // Are we quoted?
00358     if (pos < str.length() && str[pos] == QLatin1Char('"')) {
00359         QString out;
00360 
00361         // Skip the quote...
00362         ++pos;
00363 
00364         // when quoted we also need an end-quote
00365         bool endquote = false;
00366 
00367         // Parse until trailing quote...
00368         while (pos < str.length()) {
00369             if (str[pos] == QLatin1Char('\\') && pos + 1 < str.length()) {
00370                 // quoted-pair = "\" CHAR
00371                 out += str[pos + 1];
00372                 pos += 2; // Skip both...
00373             } else if (str[pos] == QLatin1Char('"')) {
00374                 ++pos;
00375                 endquote = true;
00376                 break;
00377             }  else {
00378                 out += str[pos];
00379                 ++pos;
00380             }
00381         }
00382 
00383         if( !endquote ) {
00384             pos = -1;
00385             return QString();
00386         }
00387 
00388         // Skip until term..
00389         while (pos < str.length() && (str[pos] != term)) {
00390             if( (str[pos] != QLatin1Char(' ')) && (str[pos] != QLatin1Char('\t')) ) {
00391               pos = -1;
00392               return QString();
00393             }
00394             ++pos;
00395         }
00396 
00397         if (pos < str.length()) // Stopped due to finding term
00398             ++pos;
00399 
00400         return out;
00401     } else {
00402         return extractUntil(str, term, pos, valueSpecials);
00403     }
00404 }
00405 
00406 static QMap<QString, QString> contentDispositionParser(const QString &disposition)
00407 {
00408     kDebug(7113) << "disposition: " << disposition;
00409     int pos = 0;
00410     const QString strDisposition = extractUntil(disposition, QLatin1Char(';'), pos, typeSpecials).toLower();
00411 
00412     QMap<QString, QString> parameters;
00413     QMap<QString, QString> contparams;   // all parameters that contain continuations
00414     QMap<QString, QString> encparams;    // all parameters that have character encoding
00415 
00416     // the type is invalid, the complete header is junk
00417     if( strDisposition.isEmpty() )
00418         return parameters;
00419 
00420     parameters.insert(QLatin1String("type"), strDisposition);
00421 
00422     while (pos < disposition.length()) {
00423         QString key = extractUntil(disposition, QLatin1Char('='), pos, attrSpecials).toLower();
00424 
00425         if( key.isEmpty() ) {
00426             // parse error in this key: do not parse more, but add up
00427             // everything we already got
00428             kDebug(7113) << "parse error, abort parsing";
00429             break;
00430         }
00431 
00432         int spos = key.indexOf(QLatin1Char('*'));
00433         QString val;
00434         if( key.endsWith(QLatin1Char('*')) )
00435             val = extractUntil(disposition, QLatin1Char(';'), pos, valueSpecials).toLower();
00436         else
00437             val = extractMaybeQuotedUntil(disposition, pos);
00438 
00439         if( val.isEmpty() ) {
00440             if( pos == -1 ) {
00441                 kDebug(7113) << "parse error, abort parsing";
00442                 break;
00443             }
00444             continue;
00445         }
00446 
00447         if( spos == key.length() - 1 ) {
00448             key.chop(1);
00449             encparams.insert(key, val);
00450         } else if( spos >= 0 ) {
00451             contparams.insert(key, val);
00452         } else if( parameters.contains(key) ) {
00453             kDebug(7113) << "duplicate key" << key << "found, ignoring everything more";
00454             parameters.remove(key);
00455             return parameters;
00456         } else {
00457             parameters.insert(key, val);
00458         }
00459     }
00460 
00461     QMap<QString, QString>::iterator i = contparams.begin();
00462     while( i != contparams.end() ) {
00463         QString key = i.key();
00464         int spos = key.indexOf(QLatin1Char('*'));
00465         bool hasencoding = false;
00466 
00467         if( key.at(spos + 1) != QLatin1Char('0') ) {
00468             ++i;
00469             continue;
00470         }
00471 
00472         // no leading zeros allowed, so delete the junk
00473         int klen = key.length();
00474         if( klen > spos + 2 ) {
00475             // nothing but continuations and encodings may insert * into parameter name
00476             if( (klen > spos + 3) || ((klen == spos + 3) && (key.at(spos + 2) != QLatin1Char('*'))) ) {
00477                 kDebug(7113) << "removing invalid key " << key << "with val" << i.value() << key.at(spos + 2);
00478                 i = contparams.erase(i);
00479                 continue;
00480             }
00481             hasencoding = true;
00482         }
00483 
00484         int seqnum = 1;
00485         QMap<QString, QString>::iterator partsi;
00486         // we do not need to care about encoding specifications: only the first
00487         // part is allowed to have one
00488         QString val = i.value();
00489 
00490         if (hasencoding)
00491             key.chop(2);
00492         else
00493             key.chop(1);
00494 
00495         while( (partsi = contparams.find(key + QString::number(seqnum))) != contparams.end() )
00496         {
00497             val += partsi.value();
00498             contparams.erase(partsi);
00499         }
00500 
00501         i = contparams.erase(i);
00502 
00503         key.chop(1);
00504         if (hasencoding) {
00505             encparams.insert(key, val);
00506         } else {
00507             if( parameters.contains(key) ) {
00508                 kDebug(7113) << "duplicate key" << key << "found, ignoring everything more";
00509                 parameters.remove(key);
00510                 return parameters;
00511             }
00512 
00513             parameters.insert(key, val);
00514         }
00515     }
00516 
00517     for( QMap<QString, QString>::iterator i = encparams.begin();
00518          i != encparams.end(); ++i )
00519     {
00520         QString val = i.value();
00521 
00522         // RfC 2231 encoded character set in filename
00523         int spos = val.indexOf(QLatin1Char('\''));
00524         if (spos == -1)
00525             continue;
00526         int npos = val.indexOf(QLatin1Char('\''), spos + 1);
00527         if (npos == -1)
00528             continue;
00529 
00530         const QString charset = val.left( spos );
00531         const QString lang = val.mid( spos + 1, npos - spos - 1 );
00532         const QByteArray rawval = QByteArray::fromPercentEncoding( val.mid(npos + 1).toAscii() );
00533         if( charset.isEmpty() || (charset == QLatin1String("us-ascii")) ) {
00534             bool valid = true;
00535             for( int j = rawval.length() - 1; (j >= 0) && valid; j-- )
00536                 valid = (rawval.at(j) >= 32);
00537 
00538             if( valid )
00539                 val = QString::fromAscii(rawval.constData());
00540             else
00541                 val.clear();
00542         } else {
00543             QTextCodec *codec = QTextCodec::codecForName( charset.toAscii() );
00544             if( codec )
00545                 val = codec->toUnicode( rawval );
00546             else
00547                 val.clear();
00548         }
00549 
00550         if( !val.isEmpty() ) {
00551             parameters.insert( i.key(), val );
00552         }
00553     }
00554 
00555     const QLatin1String fn("filename");
00556     if( parameters.contains(fn) ) {
00557         // Content-Disposition is not allowed to dictate directory
00558         // path, thus we extract the filename only.
00559         const QString val = QDir::toNativeSeparators( parameters[fn] );
00560         int slpos = val.lastIndexOf( QDir::separator() );
00561 
00562         if( slpos > -1 )
00563             parameters.insert(fn, val.mid( slpos + 1 ));
00564     }
00565 
00566     return parameters;
00567 }

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal