【问题标题】:Encode/Decode URLs in C++ [closed]在 C++ 中编码/解码 URL [关闭]
【发布时间】:2008-09-30 19:21:21
【问题描述】:

有人知道有什么好的 C++ 代码可以做到这一点吗?

【问题讨论】:

  • 接受一个答案怎么样?

标签: c++ urlencode urldecode percent-encoding


【解决方案1】:

前几天我遇到了这个问题的一半编码。对可用选项不满意,在查看了this C sample code 之后,我决定推出自己的 C++ url-encode 函数:

#include <cctype>
#include <iomanip>
#include <sstream>
#include <string>

using namespace std;

string url_encode(const string &value) {
    ostringstream escaped;
    escaped.fill('0');
    escaped << hex;

    for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
        string::value_type c = (*i);

        // Keep alphanumeric and other accepted characters intact
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
            continue;
        }

        // Any other characters are percent-encoded
        escaped << uppercase;
        escaped << '%' << setw(2) << int((unsigned char) c);
        escaped << nouppercase;
    }

    return escaped.str();
}

decode 函数的实现留给读者作为练习。 :P

【讨论】:

  • 我相信用“%20”替换''更通用(更普遍正确)。我已经相应地更新了代码;如果您不同意,请随时回滚。
  • 不,我同意。还借此机会删除了那个毫无意义的 setw(0) 调用(当时我认为最小宽度会保持设置,直到我将其更改回来,但实际上它在下一次输入后被重置)。
  • 我必须将 std::uppercase 添加到 "escaped
  • 看起来不对,因为不支持 UTF-8 字符串 (w3schools.com/tags/ref_urlencode.asp)。它似乎只适用于 Windows-1252
  • 问题只是isalnum(c),必须改成isalnum((unsigned char) c)
【解决方案2】:

回答我自己的问题...

libcurl 有curl_easy_escape 用于编码。

解码,curl_easy_unescape

【讨论】:

  • 你应该接受这个答案,让它显示在顶部(人们可以更容易地找到它)。
  • 你需要使用 curl 才能工作并且必须释放内存
  • 相关问题:为什么 curl 的 unescape 不能处理将“+”更改为空格?这不是 URL 解码时的标准程序吗?
【解决方案3】:
string urlDecode(string &SRC) {
    string ret;
    char ch;
    int i, ii;
    for (i=0; i<SRC.length(); i++) {
        if (int(SRC[i])==37) {
            sscanf(SRC.substr(i+1,2).c_str(), "%x", &ii);
            ch=static_cast<char>(ii);
            ret+=ch;
            i=i+2;
        } else {
            ret+=SRC[i];
        }
    }
    return (ret);
}

不是最好的,但工作正常;-)

【讨论】:

  • 当然你应该使用'%'而不是37
  • 这不会将“+”转换为空格
【解决方案4】:

cpp-netlib 有功能

namespace boost {
  namespace network {
    namespace uri {    
      inline std::string decoded(const std::string &input);
      inline std::string encoded(const std::string &input);
    }
  }
}

它们可以非常轻松地对 URL 字符串进行编码和解码。

【讨论】:

  • 天哪,谢谢。 cpp-netlib 上的文档很少。你有没有好的备忘单的链接?
【解决方案5】:

通常在编码时将 '%' 添加到 char 的 int 值将不起作用,该值应该是等效的十六进制值。例如,“/”是“%2F”而不是“%47”。

我认为这是 url 编码和解码的最佳和简洁的解决方案(没有太多的标头依赖项)。

string urlEncode(string str){
    string new_str = "";
    char c;
    int ic;
    const char* chars = str.c_str();
    char bufHex[10];
    int len = strlen(chars);

    for(int i=0;i<len;i++){
        c = chars[i];
        ic = c;
        // uncomment this if you want to encode spaces with +
        /*if (c==' ') new_str += '+';   
        else */if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') new_str += c;
        else {
            sprintf(bufHex,"%X",c);
            if(ic < 16) 
                new_str += "%0"; 
            else
                new_str += "%";
            new_str += bufHex;
        }
    }
    return new_str;
 }

string urlDecode(string str){
    string ret;
    char ch;
    int i, ii, len = str.length();

    for (i=0; i < len; i++){
        if(str[i] != '%'){
            if(str[i] == '+')
                ret += ' ';
            else
                ret += str[i];
        }else{
            sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii);
            ch = static_cast<char>(ii);
            ret += ch;
            i = i + 2;
        }
    }
    return ret;
}

【讨论】:

  • if(ic &lt; 16) new_str += "%0"; 这是干什么的?? @tormuto @reliasn
  • @Kriyen 它用于用前导零填充编码的十六进制,以防它产生单个字母;因为十六进制中的 0 到 15 是 0 到 F。
  • 我最喜欢这种方法。 +1 用于使用标准库。虽然有两个问题需要解决。我是捷克人,用过字母“ý”。结果是“%0FFFFFFC3%0FFFFFFBD”。首先使用 16 开关是不必要的,因为 utf8 保证所有尾随字节都以 10 开头,而且我的多字节似乎失败了。第二个问题是 FF,因为并非所有计算机的每个 int 的位数都相同。解决方法是跳过 16 开关(不需要)并从缓冲区中获取最后两个字符。 (我使用 stringstream 是因为我对字符串缓冲区感觉更舒服)。还是给点了。也喜欢框架
  • @Volt 您能否在新答案中发布更新后的代码?您提到了这些问题,但信息不足以进行明显的修复。
  • 这个答案有一些问题,因为它使用的是strlen。首先,这没有意义,因为我们已经知道字符串对象的大小,所以这是浪费时间。更糟糕的是,一个字符串可能包含 0 字节,这会因为 strlen 而丢失。 if(i
【解决方案6】:

[死灵法师模式开启]
在寻找快速、现代、独立于平台且优雅的解决方案时偶然发现了这个问题。不喜欢上述任何一个,cpp-netlib 将是赢家,但它在“解码”功能中存在可怕的内存漏洞。于是我想出了boost的灵气/业力解决方案。

namespace bsq = boost::spirit::qi;
namespace bk = boost::spirit::karma;
bsq::int_parser<unsigned char, 16, 2, 2> hex_byte;
template <typename InputIterator>
struct unescaped_string
    : bsq::grammar<InputIterator, std::string(char const *)> {
  unescaped_string() : unescaped_string::base_type(unesc_str) {
    unesc_char.add("+", ' ');

    unesc_str = *(unesc_char | "%" >> hex_byte | bsq::char_);
  }

  bsq::rule<InputIterator, std::string(char const *)> unesc_str;
  bsq::symbols<char const, char const> unesc_char;
};

template <typename OutputIterator>
struct escaped_string : bk::grammar<OutputIterator, std::string(char const *)> {
  escaped_string() : escaped_string::base_type(esc_str) {

    esc_str = *(bk::char_("a-zA-Z0-9_.~-") | "%" << bk::right_align(2,0)[bk::hex]);
  }
  bk::rule<OutputIterator, std::string(char const *)> esc_str;
};

上面的用法如下:

std::string unescape(const std::string &input) {
  std::string retVal;
  retVal.reserve(input.size());
  typedef std::string::const_iterator iterator_type;

  char const *start = "";
  iterator_type beg = input.begin();
  iterator_type end = input.end();
  unescaped_string<iterator_type> p;

  if (!bsq::parse(beg, end, p(start), retVal))
    retVal = input;
  return retVal;
}

std::string escape(const std::string &input) {
  typedef std::back_insert_iterator<std::string> sink_type;
  std::string retVal;
  retVal.reserve(input.size() * 3);
  sink_type sink(retVal);
  char const *start = "";

  escaped_string<sink_type> g;
  if (!bk::generate(sink, g(start), input))
    retVal = input;
  return retVal;
}

[死灵法师模式关闭]

EDIT01:修复了零填充问题 - 特别感谢 Hartmut Kaiser
EDIT02:Live on CoLiRu

【讨论】:

  • cpp-netlib 的“可怕的内存漏洞”是什么?你能提供一个简短的解释或链接吗?
  • 它(问题)已经被报告了,所以我没有报告,实际上不记得了......像尝试解析无效转义序列时访问冲突之类的东西
  • 感谢您的澄清!
【解决方案7】:

受 xperroni 启发,我编写了一个解码器。谢谢指点。

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

char from_hex(char ch) {
    return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

string url_decode(string text) {
    char h;
    ostringstream escaped;
    escaped.fill('0');

    for (auto i = text.begin(), n = text.end(); i != n; ++i) {
        string::value_type c = (*i);

        if (c == '%') {
            if (i[1] && i[2]) {
                h = from_hex(i[1]) << 4 | from_hex(i[2]);
                escaped << h;
                i += 2;
            }
        } else if (c == '+') {
            escaped << ' ';
        } else {
            escaped << c;
        }
    }

    return escaped.str();
}

int main(int argc, char** argv) {
    string msg = "J%C3%B8rn!";
    cout << msg << endl;
    string decodemsg = url_decode(msg);
    cout << decodemsg << endl;

    return 0;
}

编辑:删除了不需要的 cctype 和 iomainip 包括。

【讨论】:

  • "if (c == '%')" 块需要更多的越界检查,i[1] 和/或 i[2] 可能超出 text.end()。我也会将“转义”重命名为“未转义”。 “转义。填充('0');”可能不需要。
  • 请看我的版本。它更加优化。 pastebin.com/g0zMLpsj
【解决方案8】:

CGICC 包括进行 url 编码和解码的方法。 form_urlencode and form_urldecode

【讨论】:

  • 你刚刚在我们办公室与那个图书馆进行了一次体面的对话。
  • 这其实是最简单最正确的代码。
【解决方案9】:

在 win32 c++ 应用程序中搜索用于解码 url 的 api 时,我最终遇到了这个问题。由于问题没有完全指定平台,假设 windows 不是一件坏事。

InternetCanonicalizeUrl 是 Windows 程序的 API。更多信息here

        LPTSTR lpOutputBuffer = new TCHAR[1];
        DWORD dwSize = 1;
        BOOL fRes = ::InternetCanonicalizeUrl(strUrl, lpOutputBuffer, &dwSize, ICU_DECODE | ICU_NO_ENCODE);
        DWORD dwError = ::GetLastError();
        if (!fRes && dwError == ERROR_INSUFFICIENT_BUFFER)
        {
            delete lpOutputBuffer;
            lpOutputBuffer = new TCHAR[dwSize];
            fRes = ::InternetCanonicalizeUrl(strUrl, lpOutputBuffer, &dwSize, ICU_DECODE | ICU_NO_ENCODE);
            if (fRes)
            {
                //lpOutputBuffer has decoded url
            }
            else
            {
                //failed to decode
            }
            if (lpOutputBuffer !=NULL)
            {
                delete [] lpOutputBuffer;
                lpOutputBuffer = NULL;
            }
        }
        else
        {
            //some other error OR the input string url is just 1 char and was successfully decoded
        }

InternetCrackUrl (here) 似乎也有标志来指定是否对 url 进行解码

【讨论】:

    【解决方案10】:

    Windows API 具有由 shlwapi.dll 导出的函数 UrlEscape/UrlUnescape,用于此任务。

    【讨论】:

    • 注意:UrlEscape 不编码+
    【解决方案11】:

    为 Bill 的使用 libcurl 的建议添加后续内容:很好的建议,待更新:
    3 年后,curl_escape 功能已弃用,因此为了将来使用,最好使用curl_easy_escape

    【讨论】:

      【解决方案12】:

      我在这里找不到同时解码 2 和 3 字节序列的 URI 解码/取消转义。贡献我自己的版本,即时将 c sting 输入转换为 wstring:

      #include <string>
      
      const char HEX2DEC[55] =
      {
           0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
          -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
          -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
          -1,10,11,12, 13,14,15
      };
      
      #define __x2d__(s) HEX2DEC[*(s)-48]
      #define __x2d2__(s) __x2d__(s) << 4 | __x2d__(s+1)
      
      std::wstring decodeURI(const char * s) {
          unsigned char b;
          std::wstring ws;
          while (*s) {
              if (*s == '%')
                  if ((b = __x2d2__(s + 1)) >= 0x80) {
                      if (b >= 0xE0) { // three byte codepoint
                          ws += ((b & 0b00001111) << 12) | ((__x2d2__(s + 4) & 0b00111111) << 6) | (__x2d2__(s + 7) & 0b00111111);
                          s += 9;
                      }
                      else { // two byte codepoint
                          ws += (__x2d2__(s + 4) & 0b00111111) | (b & 0b00000011) << 6;
                          s += 6;
                      }
                  }
                  else { // one byte codepoints
                      ws += b;
                      s += 3;
                  }
              else { // no %
                  ws += *s;
                  s++;
              }
          }
          return ws;
      }
      

      【讨论】:

      • #define __x2d2__(s) (__x2d__(s) &lt;&lt; 4 | __x2d__(s+1)) 它应该使用 -WError 构建。
      • 抱歉,但“高性能”同时将单个字符添加到 wstring 是不现实的。至少reserve 有足够的空间,否则你将一直有大量的重新分配
      【解决方案13】:

      这个版本是纯 C 并且可以选择规范化资源路径。在 C++ 中使用它很简单:

      #include <string>
      #include <iostream>
      
      int main(int argc, char** argv)
      {
          const std::string src("/some.url/foo/../bar/%2e/");
          std::cout << "src=\"" << src << "\"" << std::endl;
      
          // either do it the C++ conformant way:
          char* dst_buf = new char[src.size() + 1];
          urldecode(dst_buf, src.c_str(), 1);
          std::string dst1(dst_buf);
          delete[] dst_buf;
          std::cout << "dst1=\"" << dst1 << "\"" << std::endl;
      
          // or in-place with the &[0] trick to skip the new/delete
          std::string dst2;
          dst2.resize(src.size() + 1);
          dst2.resize(urldecode(&dst2[0], src.c_str(), 1));
          std::cout << "dst2=\"" << dst2 << "\"" << std::endl;
      }
      

      输出:

      src="/some.url/foo/../bar/%2e/"
      dst1="/some.url/bar/"
      dst2="/some.url/bar/"
      

      以及实际功能:

      #include <stddef.h>
      #include <ctype.h>
      
      /**
       * decode a percent-encoded C string with optional path normalization
       *
       * The buffer pointed to by @dst must be at least strlen(@src) bytes.
       * Decoding stops at the first character from @src that decodes to null.
       * Path normalization will remove redundant slashes and slash+dot sequences,
       * as well as removing path components when slash+dot+dot is found. It will
       * keep the root slash (if one was present) and will stop normalization
       * at the first questionmark found (so query parameters won't be normalized).
       *
       * @param dst       destination buffer
       * @param src       source buffer
       * @param normalize perform path normalization if nonzero
       * @return          number of valid characters in @dst
       * @author          Johan Lindh <johan@linkdata.se>
       * @legalese        BSD licensed (http://opensource.org/licenses/BSD-2-Clause)
       */
      ptrdiff_t urldecode(char* dst, const char* src, int normalize)
      {
          char* org_dst = dst;
          int slash_dot_dot = 0;
          char ch, a, b;
          do {
              ch = *src++;
              if (ch == '%' && isxdigit(a = src[0]) && isxdigit(b = src[1])) {
                  if (a < 'A') a -= '0';
                  else if(a < 'a') a -= 'A' - 10;
                  else a -= 'a' - 10;
                  if (b < 'A') b -= '0';
                  else if(b < 'a') b -= 'A' - 10;
                  else b -= 'a' - 10;
                  ch = 16 * a + b;
                  src += 2;
              }
              if (normalize) {
                  switch (ch) {
                  case '/':
                      if (slash_dot_dot < 3) {
                          /* compress consecutive slashes and remove slash-dot */
                          dst -= slash_dot_dot;
                          slash_dot_dot = 1;
                          break;
                      }
                      /* fall-through */
                  case '?':
                      /* at start of query, stop normalizing */
                      if (ch == '?')
                          normalize = 0;
                      /* fall-through */
                  case '\0':
                      if (slash_dot_dot > 1) {
                          /* remove trailing slash-dot-(dot) */
                          dst -= slash_dot_dot;
                          /* remove parent directory if it was two dots */
                          if (slash_dot_dot == 3)
                              while (dst > org_dst && *--dst != '/')
                                  /* empty body */;
                          slash_dot_dot = (ch == '/') ? 1 : 0;
                          /* keep the root slash if any */
                          if (!slash_dot_dot && dst == org_dst && *dst == '/')
                              ++dst;
                      }
                      break;
                  case '.':
                      if (slash_dot_dot == 1 || slash_dot_dot == 2) {
                          ++slash_dot_dot;
                          break;
                      }
                      /* fall-through */
                  default:
                      slash_dot_dot = 0;
                  }
              }
              *dst++ = ch;
          } while(ch);
          return (dst - org_dst) - 1;
      }
      

      【讨论】:

      • 谢谢。这里没有可选路径的东西。 pastebin.com/RN5g7g9u
      • 这不遵循任何建议,与作者要求的相比是完全错误的(例如'+'不被空格替换)。路径规范化与 url 解码无关。如果你打算规范你的路径,你应该首先将你的 URL 分成几部分(方案、权限、路径、查询、片段),然后只在路径部分应用你喜欢的任何算法。
      【解决方案14】:

      多汁的部分

      #include <ctype.h> // isdigit, tolower
      
      from_hex(char ch) {
        return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
      }
      
      char to_hex(char code) {
        static char hex[] = "0123456789abcdef";
        return hex[code & 15];
      }
      

      注意到

      char d = from_hex(hex[0]) << 4 | from_hex(hex[1]);
      

      // %7B = '{'
      
      char d = from_hex('7') << 4 | from_hex('B');
      

      【讨论】:

        【解决方案15】:

        您可以使用 glib.h 提供的“g_uri_escape_string()”函数。 https://developer.gnome.org/glib/stable/glib-URI-Functions.html

        #include <stdio.h>
        #include <stdlib.h>
        #include <glib.h>
        int main() {
            char *uri = "http://www.example.com?hello world";
            char *encoded_uri = NULL;
            //as per wiki (https://en.wikipedia.org/wiki/Percent-encoding)
            char *escape_char_str = "!*'();:@&=+$,/?#[]"; 
            encoded_uri = g_uri_escape_string(uri, escape_char_str, TRUE);
            printf("[%s]\n", encoded_uri);
            free(encoded_uri);
        
            return 0;
        }
        

        编译它:

        gcc encoding_URI.c `pkg-config --cflags --libs glib-2.0`
        

        【讨论】:

          【解决方案16】:

          使用Facebook's folly library 可以使用另一种解决方案:folly::uriEscapefolly::uriUnescape

          【讨论】:

            【解决方案17】:

            您可以简单地使用 atlutil.h 中的函数 AtlEscapeUrl(),只需阅读其文档以了解如何使用它。

            【讨论】:

            • 这只适用于 Windows
            • 是的,我已经在 Windows 上试过了。
            【解决方案18】:

            我知道这个问题要求使用 C++ 方法,但是对于那些可能需要它的人,我想出了一个用纯 C 语言编写的非常短的函数来对字符串进行编码。它不会创建新字符串,而是更改现有字符串,这意味着它必须有足够的大小来容纳新字符串。很容易跟上。

            void urlEncode(char *string)
            {
                char charToEncode;
                int posToEncode;
                while (((posToEncode=strspn(string,"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~"))!=0) &&(posToEncode<strlen(string)))
                {
                    charToEncode=string[posToEncode];
                    memmove(string+posToEncode+3,string+posToEncode+1,strlen(string+posToEncode));
                    string[posToEncode]='%';
                    string[posToEncode+1]="0123456789ABCDEF"[charToEncode>>4];
                    string[posToEncode+2]="0123456789ABCDEF"[charToEncode&0xf];
                    string+=posToEncode+3;
                }
            }
            

            【讨论】:

              【解决方案19】:

              必须在没有 Boost 的项目中执行此操作。所以,最后写了自己的。我将它放在GitHub上:https://github.com/corporateshark/LUrlParser

              clParseURL URL = clParseURL::ParseURL( "https://name:pwd@github.com:80/path/res" );
              
              if ( URL.IsValid() )
              {
                  cout << "Scheme    : " << URL.m_Scheme << endl;
                  cout << "Host      : " << URL.m_Host << endl;
                  cout << "Port      : " << URL.m_Port << endl;
                  cout << "Path      : " << URL.m_Path << endl;
                  cout << "Query     : " << URL.m_Query << endl;
                  cout << "Fragment  : " << URL.m_Fragment << endl;
                  cout << "User name : " << URL.m_UserName << endl;
                  cout << "Password  : " << URL.m_Password << endl;
              }
              

              【讨论】:

              • 您的链接指向解析 URL 的库。它不对 URL 进行 % 编码。 (或者至少,我在源代码的任何地方都看不到 %。)因此,我认为这不能回答问题。
              猜你喜欢
              • 1970-01-01
              • 2019-06-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-22
              相关资源
              最近更新 更多