Having said that, Dart is a young language and the SDK is not mature enough to have a lot of bugs. Having solved a null pointer problem using proxies, we have a FormatException problem using cookies.

Problem description

Dio is a powerful Dart Http request library from Flutter Chinese. I used this library to write a Demo for accessing the website Login interface.

  Dio client = new Dio(options);
  client.interceptors.add(CookieManager(CookieJar()));
  
  FormData formData = new FormData.from(
      {"id": 123456."passwd": 456789."CookieDate": "2"});
  
  var response = await client.post<String> ("/path/to/login.json",
      data: formData,
  );
Copy the code

The code is simple:

  • Make a Post request with DIO, containing the username and password, and invoke the Web Login interface;
  • Server response contains set-cookie login information. Cookies are required for subsequent access.
  • The client uses the CookieManager(CookieJar()) to store cookie information.

Unfortunately, the simple code encounters the following error:

DioError [DioErrorType.DEFAULT]: FormatException: Invalid character in cookie name, code unit: ’91’ (at character 5) main[UTMPUSERID]

Capture server response analysis:

set-cookie: main[UTMPUSERID]=guest; path=/; domain=.****.net

set-cookie: main[UTMPKEY]=48990095; path=/; domain=.****.net

set-cookie: main[UTMPNUM]=79117; path=/; domain=.****.net

The set-cookie field contains invalid characters “[” and “]”, causing the request to fail.

The problem has been located, please have a barbecue server brother, change the set-cookie string definition, problem solved. ^_^

But as an aspiring programmer, you can’t help but track down the root cause.

Problem orientation

Set-cookie provisions in the protocol

In THE HTTP protocol, the characters that can be used in set-cookie are specified:

Cookie-name is defined as a token in RFC6265.

RFC2616 defines token as CHAR excluding the separator, so “[” and “]” are indeed illegal characters under the protocol.

It seems that the barbecue is useless, but there is a big gap between the protocol and reality.

In short, the RFC6265 protocol defines the latest standard that all new web interfaces should comply with. But there are plenty of legacy issues, and many sites use the original Netscape Cookie_spec. Therefore, from a practical point of view, the new interface on the server should comply with RFC6265, and the client should preferably be forward compatible with historical standards.

Dart SDK processing

Back in the Flutter code, Dio intercepts all requests and responses through the CookieManager as an Intercepter. If the response has set-cookie, it is stored in the CookieJar; Get the current Coockie from the CookieJar when making the request.

dio/lib/src/interceptors/cookie_mgr.dart _saveCookies(Response response) { ...... cookieJar.saveFromResponse( response.request.uri, cookies.map((str) => Cookie.fromSetCookieValue(str)).toList(), ); . }Copy the code

Whether cookies field legal code, therefore, included in the Dart SDK = > cookies. FromSetCookieValue:

dart-sdk/lib/_http/http_headers.dart

  void _validate() {
    const separators = const [
      "(".")"."<".">"."@".",".";".":"."\ \".'"'."/"."["."]"."?"."="."{"."}"
    ];
    for (int i = 0; i < name.length; i++) {
      int codeUnit = name.codeUnits[i];
      if (codeUnit <= 32 ||
          codeUnit >= 127 ||
          separators.indexOf(name[i]) >= 0) {
        throw new FormatException(
            "Invalid character in cookie name, code unit: '$codeUnit'", name, i); }}// Per RFC 6265, consider surrounding "" as part of the value, but otherwise
    // double quotes are not allowed.
    int start = 0;
    int end = value.length;
    if (2 <= value.length &&
        value.codeUnits[start] == 0x22 &&
        value.codeUnits[end - 1] = =0x22) {
      start++;
      end--;
    }

    for (int i = start; i < end; i++) {
      int codeUnit = value.codeUnits[i];
      if(! (codeUnit ==0x21 ||
          (codeUnit >= 0x23 && codeUnit <= 0x2B) ||
          (codeUnit >= 0x2D && codeUnit <= 0x3A) ||
          (codeUnit >= 0x3C && codeUnit <= 0x5B) ||
          (codeUnit >= 0x5D && codeUnit <= 0x7E))) {
        throw new FormatException(
            "Invalid character in cookie value, code unit: '$codeUnit'", value, i); }}}Copy the code
  • As you can see from the code above, the Dart SDK strictly implements the RFC6265 standard,
  • “(“,”) “, “<” and “>”, “@”, “, “, “;” , “:”, “\”, “”,”/”, “/”, “”,”?” ,”=”,”{“,”}” are invalid characters.
  • Note that cookie names with double quotation marks before and after Dart 2.1 are also considered illegal characters, which were later corrected when patch was mentioned.

Terminal Evasion Scheme

The server code cannot be modified because it is ancestral. We make compatibility on the client side by using our own custom Cookie instead of using the Cookie provided by the Dart SDK. This way we can customize the client’s legal characters.

Dio creates custom CookieManager, PrivateCookieManager code in this path.

client.interceptors.add(PrivateCookieManager(CookieJar())); .class PrivateCookieManager extends CookieManager {... cookieJar.saveFromResponse( response.request.uri, cookies.map((str) => _Cookie.fromSetCookieValue(str)).toList(), ); . }class _Cookie implements Cookie {
  void _validate() {
    const separators = const [
      "(".")"."<".">"."@".",".";".":"."\ \".'"'."/".//******* [] is valid in this application ***********      
[" / / ",
/ / "] ",
      "?"."="."{"."}"]; }}Copy the code

The problem summary

After tracking down, Dart SDK processing is ok and meets protocol requirements. Just processing gray level is not enough, after all, there are now a large number of server applications or the original definition. The _validate in the Cookie is a private method that can be inherited and modified if exposed, resulting in much less redundant code.