فهرست منبع

Merged from trunk
2010-02-20 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

User-defined custom request headers specified by --header option
now override builtin headers if they have same name.
* src/HttpRequest.cc
* src/HttpRequest.h
* test/HttpRequestTest.cc

2010-02-19 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

Rewirtten util::isNumber()
* src/util.cc

2010-02-19 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

Reverted changes in r1893. setlocale(LC_CTYPE, "") is needed
because without it localized error messages are not printed
correctly.
* src/Platform.cc

2010-02-19 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

Added unit tests for util::getContentDispositionFilename() from
http://greenbytes.de/tech/tc2231/ Fixed the function so that added
tests are passed.
* src/util.cc
* test/UtilTest.cc

2010-02-18 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

Removed setlocale() for LC_CTYPE. It may affect isxdigit in
util.cc
* src/Platform.cc

2010-02-18 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

Support RFC2231 "Parameter Value Character Set and Language
Information" in Content-Disposition header.
* src/HttpResponse.cc
* src/util.cc
* src/util.h
* test/UtilTest.cc

Tatsuhiro Tsujikawa 16 سال پیش
والد
کامیت
b3139e0fe6
8فایلهای تغییر یافته به همراه383 افزوده شده و 95 حذف شده
  1. 43 0
      ChangeLog
  2. 60 41
      src/HttpRequest.cc
  3. 3 2
      src/HttpRequest.h
  4. 1 1
      src/HttpResponse.cc
  5. 175 40
      src/util.cc
  6. 7 4
      src/util.h
  7. 2 2
      test/HttpRequestTest.cc
  8. 92 5
      test/UtilTest.cc

+ 43 - 0
ChangeLog

@@ -1,3 +1,46 @@
+2010-02-20  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	User-defined custom request headers specified by --header option
+	now override builtin headers if they have same name.
+	* src/HttpRequest.cc
+	* src/HttpRequest.h
+	* test/HttpRequestTest.cc
+
+2010-02-19  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Rewirtten util::isNumber()
+	* src/util.cc
+
+2010-02-19  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Reverted changes in r1893. setlocale(LC_CTYPE, "") is needed
+	because without it localized error messages are not printed
+	correctly.
+	* src/Platform.cc
+
+2010-02-19  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added unit tests for util::getContentDispositionFilename() from
+	http://greenbytes.de/tech/tc2231/ Fixed the function so that added
+	tests are passed.
+	* src/util.cc
+	* test/UtilTest.cc
+
+2010-02-18  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Removed setlocale() for LC_CTYPE. It may affect isxdigit in
+	util.cc
+	* src/Platform.cc
+
+2010-02-18  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Support RFC2231 "Parameter Value Character Set and Language
+	Information" in Content-Disposition header.
+	* src/HttpResponse.cc
+	* src/util.cc
+	* src/util.h
+	* test/UtilTest.cc
+
 2010-02-16  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 	Print CXXFLAGS

+ 60 - 41
src/HttpRequest.cc

@@ -49,6 +49,7 @@
 #include "AuthConfig.h"
 #include "a2functional.h"
 #include "TimeA2.h"
+#include "array_fun.h"
 
 namespace aria2 {
 
@@ -155,59 +156,63 @@ std::string HttpRequest::createRequest()
     requestLine += getQuery();
   }
   requestLine += " HTTP/1.1\r\n";
-  strappend(requestLine, "User-Agent: ", userAgent, "\r\n");
-  
-  requestLine += "Accept: */*"; /* */
-  for(std::deque<std::string>::const_iterator i = _acceptTypes.begin();
-      i != _acceptTypes.end(); ++i) {
-    strappend(requestLine, ",", (*i));
-  }
-  requestLine += "\r\n";
 
+  std::vector<std::pair<std::string, std::string> > builtinHds;
+  builtinHds.reserve(20);
+  builtinHds.push_back(std::make_pair("User-Agent:", userAgent));
+  std::string acceptTypes = "*/*";
+  for(std::deque<std::string>::const_iterator i = _acceptTypes.begin(),
+        end = _acceptTypes.end(); i != end; ++i) {
+    strappend(acceptTypes, ",", (*i));
+  }
+  builtinHds.push_back(std::make_pair("Accept:", acceptTypes));
   if(_contentEncodingEnabled) {
     std::string acceptableEncodings;
 #ifdef HAVE_LIBZ
     acceptableEncodings += "deflate, gzip";
 #endif // HAVE_LIBZ
     if(!acceptableEncodings.empty()) {
-      strappend(requestLine, "Accept-Encoding: ", acceptableEncodings, "\r\n");
+      builtinHds.push_back
+        (std::make_pair("Accept-Encoding:", acceptableEncodings));
     }
   }
-
-  strappend(requestLine, "Host: ", getHostText(getURIHost(), getPort()), "\r\n");
+  builtinHds.push_back
+    (std::make_pair("Host:", getHostText(getURIHost(), getPort())));
   if(_noCache) {
-    requestLine += "Pragma: no-cache\r\n";
-    requestLine += "Cache-Control: no-cache\r\n";
+    builtinHds.push_back(std::make_pair("Pragma:", "no-cache"));
+    builtinHds.push_back(std::make_pair("Cache-Control:", "no-cache"));
   }
   if(!request->isKeepAliveEnabled() && !request->isPipeliningEnabled()) {
-    requestLine += "Connection: close\r\n";
+    builtinHds.push_back(std::make_pair("Connection:", "close"));
   }
   if(!segment.isNull() && segment->getLength() > 0 && 
      (request->isPipeliningEnabled() || getStartByte() > 0)) {
-    requestLine += "Range: bytes=";
-    requestLine += util::itos(getStartByte());
-    requestLine += "-";
+    std::string rangeHeader = "bytes=";
+    rangeHeader += util::itos(getStartByte());
+    rangeHeader += "-";
     if(request->isPipeliningEnabled()) {
-      requestLine += util::itos(getEndByte());
+      rangeHeader += util::itos(getEndByte());
     }
-    requestLine += "\r\n";
+    builtinHds.push_back(std::make_pair("Range:", rangeHeader));
   }
   if(!_proxyRequest.isNull()) {
     if(request->isKeepAliveEnabled() || request->isPipeliningEnabled()) {
-      requestLine += "Proxy-Connection: Keep-Alive\r\n";
+      builtinHds.push_back(std::make_pair("Proxy-Connection:", "Keep-Alive"));
     } else {
-      requestLine += "Proxy-Connection: close\r\n";
+      builtinHds.push_back(std::make_pair("Proxy-Connection:", "close"));
     }
   }
   if(!_proxyRequest.isNull() && !_proxyRequest->getUsername().empty()) {
-    requestLine += getProxyAuthString();
+    builtinHds.push_back(getProxyAuthString());
   }
   if(!_authConfig.isNull()) {
-    strappend(requestLine, "Authorization: Basic ",
-              Base64::encode(_authConfig->getAuthText()), "\r\n");
+    builtinHds.push_back
+      (std::make_pair("Authorization:",
+                      strconcat("Basic ",
+                                Base64::encode(_authConfig->getAuthText()))));
   }
   if(getPreviousURI().size()) {
-    strappend(requestLine, "Referer: ", getPreviousURI(), "\r\n");
+    builtinHds.push_back(std::make_pair("Referer:", getPreviousURI()));
   }
   if(!_cookieStorage.isNull()) {
     std::string cookiesValue;
@@ -217,21 +222,33 @@ std::string HttpRequest::createRequest()
                                    Time().getTime(),
                                    getProtocol() == Request::PROTO_HTTPS ?
                                    true : false);
-    for(std::deque<Cookie>::const_iterator itr = cookies.begin();
-        itr != cookies.end(); ++itr) {
+    for(std::deque<Cookie>::const_iterator itr = cookies.begin(),
+          end = cookies.end(); itr != end; ++itr) {
       strappend(cookiesValue, (*itr).toString(), ";");
     }
     if(!cookiesValue.empty()) {
-      strappend(requestLine, "Cookie: ", cookiesValue, "\r\n");
+      builtinHds.push_back(std::make_pair("Cookie:", cookiesValue));
+    }
+  }
+  for(std::vector<std::pair<std::string, std::string> >::iterator i =
+        builtinHds.begin(); i != builtinHds.end(); ++i) {
+    std::vector<std::string>::const_iterator j = _headers.begin();
+    std::vector<std::string>::const_iterator jend = _headers.end();
+    for(; j != jend; ++j) {
+      if(util::startsWith(*j, (*i).first)) {
+        break;
+      }
+    }
+    if(j == jend) {
+      strappend(requestLine, (*i).first, " ", (*i).second, A2STR::CRLF);
     }
   }
   // append additional headers given by user.
-  for(std::deque<std::string>::const_iterator i = _headers.begin();
-      i != _headers.end(); ++i) {
-    strappend(requestLine, (*i), "\r\n");
+  for(std::vector<std::string>::const_iterator i = _headers.begin(),
+        end = _headers.end(); i != end; ++i) {
+    strappend(requestLine, (*i), A2STR::CRLF);
   }
-
-  requestLine += "\r\n";
+  requestLine += A2STR::CRLF;
   return requestLine;
 }
 
@@ -252,19 +269,21 @@ std::string HttpRequest::createProxyRequest() const
   //     requestLine += "Proxy-Connection: close\r\n";
   //   }
   if(!_proxyRequest->getUsername().empty()) {
-    requestLine += getProxyAuthString();
+    std::pair<std::string, std::string> auth = getProxyAuthString();
+    strappend(requestLine, auth.first, " ", auth.second, A2STR::CRLF);
   }
-  requestLine += "\r\n";
+  requestLine += A2STR::CRLF;
   return requestLine;
 }
 
-std::string HttpRequest::getProxyAuthString() const
+std::pair<std::string, std::string> HttpRequest::getProxyAuthString() const
 {
-  return strconcat("Proxy-Authorization: Basic ",
-                   Base64::encode(strconcat(_proxyRequest->getUsername(),
-                                            ":",
-                                            _proxyRequest->getPassword())),
-                   "\r\n");
+  return std::make_pair
+    ("Proxy-Authorization:",
+     strconcat("Basic ",
+               Base64::encode(strconcat(_proxyRequest->getUsername(),
+                                        ":",
+                                        _proxyRequest->getPassword()))));
 }
 
 void HttpRequest::enableContentEncoding()

+ 3 - 2
src/HttpRequest.h

@@ -40,6 +40,7 @@
 #include <cassert>
 #include <string>
 #include <deque>
+#include <vector>
 
 #include "SharedHandle.h"
 #include "Request.h"
@@ -69,7 +70,7 @@ private:
 
   std::string userAgent;
 
-  std::deque<std::string> _headers;
+  std::vector<std::string> _headers;
 
   std::deque<std::string> _acceptTypes;
 
@@ -85,7 +86,7 @@ private:
 
   bool _noCache;
 
-  std::string getProxyAuthString() const;
+  std::pair<std::string, std::string> getProxyAuthString() const;
 public:
   HttpRequest();
 

+ 1 - 1
src/HttpResponse.cc

@@ -110,7 +110,7 @@ std::string HttpResponse::determinFilename() const
   } else {
     logger->info(MSG_CONTENT_DISPOSITION_DETECTED,
                  cuid, contentDisposition.c_str());
-    return util::urldecode(contentDisposition);
+    return contentDisposition;
   }
 }
 

+ 175 - 40
src/util.cc

@@ -115,7 +115,7 @@ void split(std::pair<std::string, std::string>& hp, const std::string& src, char
   hp.second = A2STR::NIL;
   std::string::size_type p = src.find(delim);
   if(p == std::string::npos) {
-    hp.first = src;
+    hp.first = trim(src);
     hp.second = A2STR::NIL;
   } else {
     hp.first = trim(src.substr(0, p));
@@ -130,7 +130,7 @@ std::pair<std::string, std::string> split(const std::string& src, const std::str
   hp.second = A2STR::NIL;
   std::string::size_type p = src.find_first_of(delims);
   if(p == std::string::npos) {
-    hp.first = src;
+    hp.first = trim(src);
     hp.second = A2STR::NIL;
   } else {
     hp.first = trim(src.substr(0, p));
@@ -201,6 +201,21 @@ std::string replace(const std::string& target, const std::string& oldstr, const
   return result;
 }
 
+bool isAlpha(const char c)
+{
+  return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+}
+
+bool isDigit(const char c)
+{
+  return '0' <= c && c <= '9';
+}
+
+bool isHexDigit(const char c)
+{
+  return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
+}
+
 bool inRFC3986ReservedChars(const char c)
 {
   static const char reserved[] = {
@@ -214,15 +229,34 @@ bool inRFC3986ReservedChars(const char c)
 bool inRFC3986UnreservedChars(const char c)
 {
   static const char unreserved[] = { '-', '.', '_', '~' };
-  return
-    // ALPHA
-    ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') ||
-    // DIGIT
-    ('0' <= c && c <= '9') ||
+  return isAlpha(c) || isDigit(c) ||
     std::find(&unreserved[0], &unreserved[arrayLength(unreserved)], c) !=
     &unreserved[arrayLength(unreserved)];
 }
 
+bool inRFC2978MIMECharset(const char c)
+{
+  static const char chars[] = {
+    '!', '#', '$', '%', '&',
+    '\'', '+', '-', '^', '_',
+    '`', '{', '}', '~'
+  };
+  return isAlpha(c) || isDigit(c) ||
+    std::find(&chars[0], &chars[arrayLength(chars)], c) !=
+    &chars[arrayLength(chars)];
+}
+
+bool inRFC2616HttpToken(const char c)
+{
+  static const char chars[] = {
+    '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.',
+    '^', '_', '`', '|', '~'
+  };
+  return isAlpha(c) || isDigit(c) ||
+    std::find(&chars[0], &chars[arrayLength(chars)], c) !=
+    &chars[arrayLength(chars)];
+}
+
 std::string urlencode(const unsigned char* target, size_t len) {
   std::string dest;
   for(size_t i = 0; i < len; ++i) {
@@ -244,9 +278,7 @@ std::string urlencode(const std::string& target)
 std::string torrentUrlencode(const unsigned char* target, size_t len) {
   std::string dest;
   for(size_t i = 0; i < len; ++i) {
-    if(('0' <= target[i] && target[i] <= '9') ||
-       ('A' <= target[i] && target[i] <= 'Z') ||
-       ('a' <= target[i] && target[i] <= 'z')) {
+    if(isAlpha(target[i]) || isDigit(target[i])) {
       dest += target[i];
     } else {
       dest.append(StringFormat("%%%02X", target[i]).str());
@@ -267,7 +299,7 @@ std::string urldecode(const std::string& target) {
       itr != target.end(); ++itr) {
     if(*itr == '%') {
       if(itr+1 != target.end() && itr+2 != target.end() &&
-         isxdigit(*(itr+1)) && isxdigit(*(itr+2))) {
+         isHexDigit(*(itr+1)) && isHexDigit(*(itr+2))) {
         result += parseInt(std::string(itr+1, itr+3), 16);
         itr += 2;
       } else {
@@ -610,36 +642,139 @@ void parsePrioritizePieceRange
   result.insert(result.end(), indexes.begin(), indexes.end());
 }
 
-std::string getContentDispositionFilename(const std::string& header) {
-  static const std::string keyName = "filename=";
-  std::string::size_type attributesp = header.find(keyName);
-  if(attributesp == std::string::npos) {
-    return A2STR::NIL;
-  }
-  std::string::size_type filenamesp = attributesp+keyName.size();
-  std::string::size_type filenameep;
-  if(filenamesp == header.size()) {
-    return A2STR::NIL;
-  }
-  
-  if(header[filenamesp] == '\'' || header[filenamesp] == '"') {
-    char quoteChar = header[filenamesp];
-    filenameep = header.find(quoteChar, filenamesp+1);
-  } else {
-    filenameep = header.find(';', filenamesp);
+static std::string trimBasename(const std::string& src)
+{
+  static const std::string TRIMMED("\r\n\t '\"");
+  std::string fn = File(trim(src, TRIMMED)).getBasename();
+  std::string::iterator enditer = std::remove(fn.begin(), fn.end(), '\\');
+  fn = std::string(fn.begin(), enditer);
+  if(fn == ".." || fn == A2STR::DOT_C) {
+    fn = A2STR::NIL;
   }
-  if(filenameep == std::string::npos) {
-    filenameep = header.size();
+  return fn;
+}
+
+// Converts ISO/IEC 8859-1 string to UTF-8 string.  If there is a
+// character not in ISO/IEC 8859-1, returns empty string.
+std::string iso8859ToUtf8(const std::string& src)
+{
+  std::string dest;
+  for(std::string::const_iterator itr = src.begin(); itr != src.end(); ++itr) {
+    unsigned char c = *itr;
+    if(0xa0 <= c && c <= 0xff) {
+      if(c <= 0xbf) {
+        dest += 0xc2;
+      } else {
+        dest += 0xc3;
+      }
+      dest += c&(~0x40);
+    } else if(0x80 <= c && c <= 0x9f) {
+      return A2STR::NIL;
+    } else {
+      dest += c;
+    }
   }
-  static const std::string TRIMMED("\r\n '\"");
-  std::string fn =
-    File(trim(header.substr
-              (filenamesp, filenameep-filenamesp), TRIMMED)).getBasename();
-  if(fn == ".." || fn == A2STR::DOT_C) {
-    return A2STR::NIL;
-  } else {
-    return fn;
+  return dest;
+}
+
+std::string getContentDispositionFilename(const std::string& header)
+{
+  std::string filename;
+  std::vector<std::string> params;
+  split(header, std::back_inserter(params), A2STR::SEMICOLON_C, true);
+  for(std::vector<std::string>::iterator i = params.begin();
+      i != params.end(); ++i) {
+    std::string& param = *i;
+    static const std::string keyName = "filename";
+    if(!startsWith(toLower(param), keyName) || param.size() == keyName.size()) {
+      continue;
+    }
+    std::string::iterator markeritr = param.begin()+keyName.size();
+    if(*markeritr == '*') {
+      // See RFC2231 Section4 and draft-reschke-rfc2231-in-http.
+      // Please note that this function doesn't do charset conversion
+      // except that if iso-8859-1 is specified, it is converted to
+      // utf-8.
+      ++markeritr;
+      for(; markeritr != param.end() && *markeritr == ' '; ++markeritr);
+      if(markeritr == param.end() || *markeritr != '=') {
+        continue;
+      }
+      std::pair<std::string, std::string> paramPair;
+      split(paramPair, param, '=');
+      std::string value = paramPair.second;
+      std::vector<std::string> extValues;
+      split(value, std::back_inserter(extValues), "'", false, true);
+      if(extValues.size() != 3) {
+        continue;
+      }
+      bool bad = false;
+      const std::string& charset = extValues[0];
+      for(std::string::const_iterator j = charset.begin(); j != charset.end();
+          ++j) {
+        // Since we first split parameter by ', we can safely assume
+        // that ' is not included in charset.
+        if(!inRFC2978MIMECharset(*j)) {
+          bad = true;
+          break;
+        }
+      }
+      if(bad) {
+        continue;
+      }
+      bad = false;
+      value = extValues[2];
+      for(std::string::const_iterator j = value.begin(); j != value.end(); ++j){
+        if(*j == '%') {
+          if(j+1 != value.end() && isHexDigit(*(j+1)) &&
+             j+2 != value.end() && isHexDigit(*(j+2))) {
+            j += 2;
+          } else {
+            bad = true;
+            break;
+          }
+        } else {
+          if(*j == '*' || *j == '\'' || !inRFC2616HttpToken(*j)) {
+            bad = true;
+            break;
+          }
+        }
+      }
+      if(bad) {
+        continue;
+      }
+      value = trimBasename(urldecode(value));
+      if(toLower(extValues[0]) == "iso-8859-1") {
+        value = iso8859ToUtf8(value);
+      }
+      filename = value;
+      break;
+    } else {
+      for(; markeritr != param.end() && *markeritr == ' '; ++markeritr);
+      if(markeritr == param.end() || *markeritr != '=') {
+        continue;
+      }
+      std::pair<std::string, std::string> paramPair;
+      split(paramPair, param, '=');
+      std::string value = paramPair.second;
+      if(value.empty()) {
+        continue;
+      }
+      std::string::iterator filenameLast;
+      if(*value.begin() == '\'' || *value.begin() == '"') {
+        char qc = *value.begin();
+        for(filenameLast = value.begin()+1;
+            filenameLast != value.end() && *filenameLast != qc;
+            ++filenameLast);
+      } else {
+        filenameLast = value.end();
+      }
+      value = trimBasename(urldecode(std::string(value.begin(), filenameLast)));
+      filename = value;
+      // continue because there is a chance we can find filename*=...
+    }
   }
+  return filename;
 }
 
 std::string randomAlpha(size_t length, const RandomizerHandle& randomizer) {
@@ -801,8 +936,8 @@ bool isNumber(const std::string& what)
   if(what.empty()) {
     return false;
   }
-  for(uint32_t i = 0; i < what.size(); ++i) {
-    if(!isdigit(what[i])) {
+  for(std::string::const_iterator i = what.begin(); i != what.end(); ++i) {
+    if(!isDigit(*i)) {
       return false;
     }
   }

+ 7 - 4
src/util.h

@@ -212,7 +212,9 @@ void parsePrioritizePieceRange
  size_t pieceLength,
  uint64_t defaultSize = 1048576 /* 1MiB */);
 
-// this function temporarily put here
+// Converts ISO/IEC 8859-1 string src to utf-8.
+std::string iso8859ToUtf8(const std::string& src);
+
 std::string getContentDispositionFilename(const std::string& header);
 
 std::string randomAlpha(size_t length,
@@ -317,7 +319,8 @@ std::map<size_t, std::string> createIndexPathMap(std::istream& i);
  */
 template<typename OutputIterator>
 OutputIterator split(const std::string& src, OutputIterator out,
-                     const std::string& delims, bool doTrim = false)
+                     const std::string& delims, bool doTrim = false,
+                     bool allowEmpty = false)
 {
   std::string::size_type p = 0;
   while(1) {
@@ -327,7 +330,7 @@ OutputIterator split(const std::string& src, OutputIterator out,
       if(doTrim) {
         term = util::trim(term);
       }
-      if(!term.empty()) {
+      if(allowEmpty || !term.empty()) {
         *out = term;
         ++out;
       }
@@ -338,7 +341,7 @@ OutputIterator split(const std::string& src, OutputIterator out,
       term = util::trim(term);
     }
     p = np+1;
-    if(!term.empty()) {
+    if(allowEmpty || !term.empty()) {
       *out = term;
       ++out;
     }

+ 2 - 2
test/HttpRequestTest.cc

@@ -663,16 +663,16 @@ void HttpRequestTest::testAddHeader()
   httpRequest.setRequest(request);
   httpRequest.setAuthConfigFactory(_authConfigFactory, _option.get());
   httpRequest.addHeader("X-ARIA2: v0.13\nX-ARIA2-DISTRIBUTE: enabled\n");
-
+  httpRequest.addHeader("Accept: text/html");
   std::string expectedText = "GET /archives/aria2-1.0.0.tar.bz2 HTTP/1.1\r\n"
     "User-Agent: aria2\r\n"
-    "Accept: */*\r\n"
     "Host: localhost\r\n"
     "Pragma: no-cache\r\n"
     "Cache-Control: no-cache\r\n"
     "Connection: close\r\n"
     "X-ARIA2: v0.13\r\n"
     "X-ARIA2-DISTRIBUTE: enabled\r\n"
+    "Accept: text/html\r\n"
     "\r\n";
 
   CPPUNIT_ASSERT_EQUAL(expectedText, httpRequest.createRequest());

+ 92 - 5
test/UtilTest.cc

@@ -154,11 +154,11 @@ void UtilTest::testSplit() {
 }
 
 void UtilTest::testSplit_many() {
-  std::deque<std::string> v1;
+  std::vector<std::string> v1;
   util::split("name1=value1; name2=value2; name3=value3",std::back_inserter(v1),
               ";", true);
-  CPPUNIT_ASSERT_EQUAL(3, (int)v1.size());
-  std::deque<std::string>::iterator itr = v1.begin();
+  CPPUNIT_ASSERT_EQUAL((size_t)3, v1.size());
+  std::vector<std::string>::iterator itr = v1.begin();
   CPPUNIT_ASSERT_EQUAL(std::string("name1=value1"), *itr++);
   CPPUNIT_ASSERT_EQUAL(std::string("name2=value2"), *itr++);
   CPPUNIT_ASSERT_EQUAL(std::string("name3=value3"), *itr++);
@@ -167,11 +167,28 @@ void UtilTest::testSplit_many() {
 
   util::split("name1=value1; name2=value2; name3=value3",std::back_inserter(v1),
               ";", false);
-  CPPUNIT_ASSERT_EQUAL(3, (int)v1.size());
+  CPPUNIT_ASSERT_EQUAL((size_t)3, v1.size());
   itr = v1.begin();
   CPPUNIT_ASSERT_EQUAL(std::string("name1=value1"), *itr++);
   CPPUNIT_ASSERT_EQUAL(std::string(" name2=value2"), *itr++);
   CPPUNIT_ASSERT_EQUAL(std::string(" name3=value3"), *itr++);
+
+  v1.clear();
+
+  util::split("k=v", std::back_inserter(v1), ";", false, true);
+  CPPUNIT_ASSERT_EQUAL((size_t)1, v1.size());
+  CPPUNIT_ASSERT_EQUAL(std::string("k=v"), v1[0]);
+
+  v1.clear();
+
+  util::split(" ", std::back_inserter(v1), ";", true, true);
+  CPPUNIT_ASSERT_EQUAL((size_t)1, v1.size());
+  CPPUNIT_ASSERT_EQUAL(std::string(""), v1[0]);
+
+  v1.clear();
+
+  util::split(" ", std::back_inserter(v1), ";", true);
+  CPPUNIT_ASSERT_EQUAL((size_t)0, v1.size());
 }
 
 void UtilTest::testEndsWith() {
@@ -276,7 +293,8 @@ void UtilTest::testGetContentDispositionFilename() {
   CPPUNIT_ASSERT_EQUAL(std::string("aria2.tar.bz2"), util::getContentDispositionFilename(h8));
 
   std::string h9 = "attachment; filename=\"aria2.tar.bz2; creation-date=20 Jun 2007 00:00:00 GMT\"";
-  CPPUNIT_ASSERT_EQUAL(std::string("aria2.tar.bz2; creation-date=20 Jun 2007 00:00:00 GMT"), util::getContentDispositionFilename(h9));
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2.tar.bz2"),
+                       util::getContentDispositionFilename(h9));
 
   std::string h10 = "attachment; filename=";
   CPPUNIT_ASSERT_EQUAL(std::string(""), util::getContentDispositionFilename(h10));
@@ -295,6 +313,75 @@ void UtilTest::testGetContentDispositionFilename() {
   std::string currentDir = "attachment; filename=.";
   CPPUNIT_ASSERT_EQUAL(std::string(),
                        util::getContentDispositionFilename(currentDir));
+  // RFC2231 Section4
+  std::string extparam2 = "filename*=''aria2";
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2"),
+                       util::getContentDispositionFilename(extparam2));
+  std::string extparam3 = "filename*='''";
+  CPPUNIT_ASSERT_EQUAL(std::string(""),
+                       util::getContentDispositionFilename(extparam3));
+  std::string extparam4 = "filename*='aria2";
+  CPPUNIT_ASSERT_EQUAL(std::string(""),
+                       util::getContentDispositionFilename(extparam4));
+  std::string extparam5 = "filename*='''aria2";
+  CPPUNIT_ASSERT_EQUAL(std::string(""),
+                       util::getContentDispositionFilename(extparam5));
+  std::string extparam6 = "filename*";
+  CPPUNIT_ASSERT_EQUAL(std::string(""),
+                       util::getContentDispositionFilename(extparam6));
+  std::string extparam7 = "filename*=UTF-8''aria2;filename=hello%20world";
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2"),
+                       util::getContentDispositionFilename(extparam7));
+  std::string extparam8 = "filename=aria2;filename*=UTF-8''hello%20world";
+  CPPUNIT_ASSERT_EQUAL(std::string("hello world"),
+                       util::getContentDispositionFilename(extparam8));
+  std::string extparam9 = "filename*=ISO-8859-1''%A3";
+  std::string extparam9ans;
+  extparam9ans += 0xc2;
+  extparam9ans += 0xa3;
+  CPPUNIT_ASSERT_EQUAL(extparam9ans,
+                       util::getContentDispositionFilename(extparam9));
+
+  // Tests from http://greenbytes.de/tech/tc2231/
+  // attwithasciifnescapedchar
+  CPPUNIT_ASSERT_EQUAL
+    (std::string("foo.html"),
+     util::getContentDispositionFilename("filename=\"f\\oo.html\""));
+  // attwithasciifilenameucase
+  CPPUNIT_ASSERT_EQUAL
+    (std::string("foo.html"),
+     util::getContentDispositionFilename("FILENAME=\"foo.html\""));
+  // attwithisofn2231iso
+  CPPUNIT_ASSERT_EQUAL
+    (std::string("foo-ä.html"),
+     util::getContentDispositionFilename("filename*=iso-8859-1''foo-%E4.html"));
+  // attwithfn2231utf8
+  CPPUNIT_ASSERT_EQUAL
+    (std::string("foo-ä-€.html"),
+     util::getContentDispositionFilename
+     ("filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html"));
+  // attwithfn2231utf8-bad
+  CPPUNIT_ASSERT_EQUAL
+    (std::string(""),
+     util::getContentDispositionFilename
+     ("filename*=iso-8859-1''foo-%c3%a4-%e2%82%ac.html"));
+  // attwithfn2231ws1
+  CPPUNIT_ASSERT_EQUAL
+    (std::string(""),
+     util::getContentDispositionFilename("filename *=UTF-8''foo-%c3%a4.html"));
+  // attwithfn2231ws2
+  CPPUNIT_ASSERT_EQUAL
+    (std::string("foo-ä.html"),
+     util::getContentDispositionFilename("filename*= UTF-8''foo-%c3%a4.html"));
+  // attwithfn2231ws3
+  CPPUNIT_ASSERT_EQUAL
+    (std::string("foo-ä.html"),
+     util::getContentDispositionFilename("filename* =UTF-8''foo-%c3%a4.html"));
+  // attwithfn2231quot
+  CPPUNIT_ASSERT_EQUAL
+    (std::string(""),
+     util::getContentDispositionFilename
+     ("filename*=\"UTF-8''foo-%c3%a4.html\""));
 }
 
 class Printer {