docs: public static String decode(String s, String enc, boolean decodeSlash, boolean formEncoding, StringBuilder buffer) {
          buffer.setLength(0);
          boolean needToChange = false;
          int numChars = s.length();
          int i = 0;
  
          while (i < numChars) {
              char c = s.charAt(i);
              if (c == '+') {
                  if (formEncoding) {
                      buffer.append(' ');
                      i++;
                      needToChange = true;
                  } else {
                      i++;
                      buffer.append(c);
                  }
              } else if (c == '%' || c > 127) {
                  /*
                   * Starting with this instance of a character
                   * that needs to be encoded, process all
                   * consecutive substrings of the form %xy. Each
                   * substring %xy will yield a byte. Convert all
                   * consecutive  bytes obtained this way to whatever
                   * character(s) they represent in the provided
                   * encoding.
                   *
                   * Note that we need to decode the whole rest of the value, we can't just decode
                   * three characters. For multi code point characters there if the code point can be
                   * represented as an alphanumeric
                   */
                  try {
                      // guess the size of the remaining bytes
                      // of remaining bytes
                      // this works for percent encoded characters,
                      // not so much for unencoded bytes
                      byte[] bytes = new byte[numChars - i + 1];
  
                      int pos = 0;
  
                      while ((i < numChars)) {
                          if (c == '%') {
                              char p1 = Character.toLowerCase(s.charAt(i + 1));
                              char p2 = Character.toLowerCase(s.charAt(i + 2));
                              if (!decodeSlash && ((p1 == '2' && p2 == 'f') || (p1 == '5' && p2 == 'c'))) {
                                  if(pos + 2 >= bytes.length) {
                                      bytes = expandBytes(bytes);
                                  }
                                  bytes[pos++] = (byte) c;
                                  // should be copied with preserved upper/lower case
                                  bytes[pos++] = (byte) s.charAt(i + 1);
                                  bytes[pos++] = (byte) s.charAt(i + 2);
                                  i += 3;
  
                                  if (i < numChars) {
                                      c = s.charAt(i);
                                  }
                                  continue;
                              }
                              int v = 0;
                              if (p1 >= '0' && p1 <= '9') {
                                  v = (p1 - '0') << 4;
                              } else if (p1 >= 'a' && p1 <= 'f') {
                                  v = (p1 - 'a' + 10) << 4;
                              } else {
                                  throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null);
                              }
                              if (p2 >= '0' && p2 <= '9') {
                                  v += (p2 - '0');
                              } else if (p2 >= 'a' && p2 <= 'f') {
                                  v += (p2 - 'a' + 10);
                              } else {
                                  throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null);
                              }
                              if (v < 0) {
                                  throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, null);
                              }
  
                              if(pos == bytes.length) {
                                  bytes = expandBytes(bytes);
                              }
                              bytes[pos++] = (byte) v;
                              i += 3;
                              if (i < numChars) {
                                  c = s.charAt(i);
                              }
                          } else if (c == '+' && formEncoding) {
                              if(pos == bytes.length) {
                                  bytes = expandBytes(bytes);
                              }
                              bytes[pos++] = (byte) ' ';
                              ++i;
                              if (i < numChars) {
                                  c = s.charAt(i);
                              }
                          } else {
                              if (pos == bytes.length) {
                                  bytes = expandBytes(bytes);
                              }
                              ++i;
                              if(c >> 8 != 0) {
                                  bytes[pos++] = (byte) (c >> 8);
                                  if (pos == bytes.length) {
                                      bytes = expandBytes(bytes);
                                  }
                                  bytes[pos++] = (byte) c;
                              } else {
                                  bytes[pos++] = (byte) c;
                                  if (i < numChars) {
                                      c = s.charAt(i);
                                  }
                              }
  
                          }
                      }
  
                      String decoded = new String(bytes, 0, pos, enc);
                      buffer.append(decoded);
                  } catch (NumberFormatException e) {
                      throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, e);
                  } catch (UnsupportedEncodingException e) {
                      throw UndertowMessages.MESSAGES.failedToDecodeURL(s, enc, e);
                  }
                  needToChange = true;
                  break;
              } else {
                  buffer.append(c);
                  i++;
              }
          }
  
          return (needToChange ? buffer.toString() : s);
      }