C ++로 URL 인코딩 / 디코딩 [닫힘]


85

누구든지 이것을 수행하는 좋은 C ++ 코드를 알고 있습니까?


3
답변을받는 것은 어떻습니까?
gsamaras

답변:


81

나는 요 전에이 문제의 인코딩 절반에 직면했다. 사용 가능한 옵션 마음에 들지 않고이 C 샘플 코드를 살펴본 후 내 자신의 C ++ URL 인코딩 기능을 사용하기로 결정했습니다.

#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();
}

디코딩 기능의 구현은 독자에게 연습으로 남겨집니다. :피


1
''를 '% 20'으로 바꾸는 것이보다 일반적 (보다 일반적으로 정확함)이라고 생각합니다. 그에 따라 코드를 업데이트했습니다. 동의하지 않으면 언제든지 되돌릴 수 있습니다.
Josh Kelley

1
아니, 동의합니다. 또한 그 무의미한 setw(0)호출 을 제거 할 기회를 잡았습니다 (당시에는 최소 너비가 다시 변경 될 때까지 설정되어 있다고 생각했지만 실제로는 다음 입력 후에 재설정됩니다).
xperroni

1
"escaped << '%'<< std :: uppercase << std :: setw (2) << int ((unsigned char) c);"줄에 std :: uppercase를 추가해야했습니다. 경우에는 다른 사람이 대신 %의 3A의 예를 들어 %의 3A이 반환 이유를 궁금해
gumlym

2
UTF-8 문자열이 지원되지 않기 때문에 잘못된 것 같습니다 ( w3schools.com/tags/ref_urlencode.asp ). Windows-1252에서만 작동하는 것 같습니다
Skywalker13

1
문제는 단지 isalnum(c), 변경해야합니다isalnum((unsigned char) c)
Skywalker13

74

내 질문에 답하는 중 ...

libcurl에는 인코딩을위한 curl_easy_escape 가 있습니다.

디코딩을 위해 curl_easy_unescape


4
이 답변을 수락해야 상단에 표시되고 사람들이 쉽게 찾을 수 있습니다.
Mouagip

이 작업을 위해 curl을 사용해야하고 메모리를
확보

관련 질문 : curl의 unescape가 '+'를 공백으로 변경하는 것을 처리하지 않는 이유는 무엇입니까? URL 디코딩시 표준 절차가 아닙니까?
Stéphane

12
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);
}

최고는 아니지만 잘 작동합니다 ;-)


5
물론 당신은 사용해야 '%'대신 37.
John Zwinck 2014 년

4
이 공간에 '+'변환하지 않습니다
xryl669

11

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 문자열을 매우 쉽게 인코딩하고 디코딩 할 수 있습니다.


2
감사합니다. cpp-netlib에 대한 문서는 드물다. 좋은 치트 시트에 대한 링크가 있습니까?
user249806 2013 년

8

일반적으로 char의 int 값에 '%'를 추가하는 것은 인코딩 할 때 작동하지 않으며 값은 16 진수로 간주됩니다. 예 : '/'는 '% 47'이 아닌 '% 2F'입니다.

나는 이것이 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 < 16) new_str += "%0"; 이 음식은 무엇입니까 ?? @tormuto의 @reliasn
KriyenKP

1
@Kriyen 단일 문자가되는 경우 인코딩 된 HEX를 선행 0으로 채우는 데 사용됩니다. HEX에서 0에서 15는 0에서 F까지입니다.
tormuto

1
이 접근 방식이 가장 좋습니다. 표준 라이브러리 사용에 +1. 해결해야 할 두 가지 문제가 있습니다. 나는 체코 사람이고 문자 "ý"를 사용했습니다. 결과는 "% 0FFFFFFC3 % 0FFFFFFBD"입니다. utf8은 모든 후행 바이트를 10으로 시작하도록 보장하고 내 멀티 바이트가 실패한 것처럼 보였으므로 먼저 16 스위치를 사용할 필요가 없습니다. 두 번째 문제는 FF입니다. 모든 컴퓨터가 int 당 동일한 양의 비트를 가지고있는 것은 아니기 때문입니다. 수정 사항은 16 스위치 (필요하지 않음)를 건너 뛰고 버퍼에서 마지막 두 문자를 가져 오는 것입니다. (나는 문자열 버퍼와 더 편안하게 느끼기 때문에 stringstream을 사용했습니다). 여전히 지적했다. 프레임도 마찬가지로
Volt

@Volt 새 답변에 업데이트 된 코드를 게시 할 수 있습니까? 문제를 언급했지만 명백한 수정을위한 충분한 정보가 아닙니다.
gregn3

이 답변은 strlen을 사용하기 때문에 몇 가지 문제가 있습니다. 첫째, 이것은 우리가 이미 문자열 객체의 크기를 알고 있기 때문에 이치에 맞지 않습니다. 그래서 시간 낭비입니다. 하지만 훨씬 더 나쁜 것은 문자열에 0 바이트가 포함될 수 있으며 이는 strlen으로 인해 손실 될 수 있다는 것입니다. 또한 if (i <16)는 "%%% 02X"를 사용하여 printf 자체로 덮을 수 있기 때문에 비효율적입니다. 마지막으로 c는 부호없는 바이트 여야합니다. 그렇지 않으면 @Volt가 선행 '0xFFF ...'로 설명하는 효과를 얻습니다.
Devolus

8

[Necromancer 모드 켜짐]
빠르고 현대적인 플랫폼 독립적이고 우아한 솔루션을 찾고있을 때이 질문을 발견했습니다. 위와 달리 cpp-netlib가 승자가 될 것이지만 "디코딩 된"기능에 끔찍한 메모리 취약점이 있습니다. 그래서 부스트의 정신 기 / 카르마 솔루션을 생각해 냈습니다.

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무엇입니까? 간단한 설명이나 링크를 제공 할 수 있습니까?
Craig M. Brandenburg

기억 해달라고 I didnt 한 보고서 때문에 실제로 그것은 (문제는) 이미보고되었다 ... 액세스 위반이 뭔가를 잘못 이스케이프 시퀀스, 또는 어떤 구문 분석하려고 할 때
kreuzerkrieg


명확히 해주셔서 감사합니다!
Craig M. Brandenburg


6

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 포함을 제거했습니다.


1
"if (c == '%')"블록은 더 많은 범위를 벗어난 검사가 필요합니다. i [1] 및 / 또는 i [2]는 text.end () 이상일 수 있습니다. "escaped"의 이름도 "unscaped"로 변경합니다. "escaped.fill ( '0');" 아마도 불필요합니다.
roalz

제 버전을보세요. 더 최적화되어 있습니다. pastebin.com/g0zMLpsj
KoD

4

libcurl 사용에 대한 Bill의 권장 사항에 대한 후속 조치 추가 : 훌륭한 제안 및 업데이트 예정 :
3 년 후 curl_escape 함수는 더 이상 사용되지 않으므로 향후 사용을 위해 curl_easy_escape 를 사용하는 것이 좋습니다 .


4

win32 C ++ 앱에서 URL을 디코딩하는 API를 검색 할 때이 질문에 끝났습니다. 질문은 창이 나쁜 것이 아니라고 가정하는 플랫폼을 지정하지 않기 때문에.

InternetCanonicalizeUrl은 Windows 프로그램 용 API입니다. 여기에 더 많은 정보

        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 ( 여기 )에도 URL 디코딩 여부를 지정하는 플래그가있는 것 같습니다.


3

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) << 4 | __x2d__(s+1))-WError로 빌드됩니다.
Janek Olszak

미안하지만 단일 문자를 추가하는 동안 "고성능" wstring은 비현실적입니다. 적어도 reserve충분한 공간, 그렇지 않으면 당신은 대규모 재 할당 모든 시간이있을 것이다
펠릭스 Dombek


1

이 버전은 순수 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을 부분 (스키마, 권한, 경로, 쿼리, 조각)으로 분할 한 다음 원하는 알고리즘을 경로 부분에만 적용해야합니다.
xryl669 2015

1

육즙 비트

#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');

1

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`


0

질문이 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;
    }
}

0

atlutil.h의 AtlEscapeUrl () 함수를 사용하기 만하면됩니다. 사용 방법에 대한 문서를 살펴보세요.


1
창에이 것 만 일
kritzikratzi

예, 나는 창문에서 이것을 시도했습니다.
Pratik

-2

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을 % 인코딩하지 않습니다. (또는 적어도 소스의 어느 곳에서도 %를 볼 수 없었습니다.) 따라서이 질문에 대한 답이 없다고 생각합니다.
마틴 보너 모니카 지원
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.