Codificar / decodificar URLs em C ++ [fechado]

Respostas:

83

Eu enfrentei a metade da codificação desse problema outro dia. Insatisfeito com as opções disponíveis, e depois de dar uma olhada neste código de exemplo C , decidi lançar minha própria função de codificação de url em C ++:

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

A implementação da função de decodificação é deixada como um exercício para o leitor. : P

xperroni
fonte
1
Acredito que seja mais genérico (mais geralmente correto) substituir '' por "% 20". Eu atualizei o código de acordo; sinta-se à vontade para reverter se discordar.
Josh Kelley
1
Nah, eu concordo. Também aproveitei para remover aquela setw(0)chamada inútil (na época, pensei que a largura mínima permaneceria definida até que eu alterasse de volta, mas na verdade é redefinida após a próxima entrada).
xperroni
1
Tive que adicionar std :: uppercase à linha "escapou << '%' << std :: uppercase << std :: setw (2) << int ((unsigned char) c);" Caso outras pessoas estejam se perguntando por que isso retorna, por exemplo% 3a em vez de% 3A
gumlym
2
Parece errado porque strings UTF-8 não são suportadas ( w3schools.com/tags/ref_urlencode.asp ). Parece funcionar apenas para Windows-1252
Skywalker13
1
O problema era apenas isalnum(c), ele deve ser alterado paraisalnum((unsigned char) c)
Skywalker13
76

Respondendo minha própria pergunta ...

libcurl tem curl_easy_escape para codificação.

Para decodificar, curl_easy_unescape

user126593
fonte
4
Você deve aceitar essa resposta para que seja exibida no topo (e as pessoas possam achar mais fácil).
Mouagip
você precisa usar curl para que isso funcione e tem que liberar a memória
xinthose
Pergunta relacionada: por que o unescape de curl não funciona com a mudança de '+' para espaço? Não é esse o procedimento padrão para a decodificação de URL?
Stéphane de
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);
}

não é o melhor, mas está funcionando bem ;-)


fonte
5
Claro que você deve usar em '%'vez de 37.
John Zwinck
4
Isso não converte '+' em espaço
xryl669
11

cpp-netlib tem funções

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

eles permitem codificar e decodificar strings de URL muito facilmente.

Yuriy Petrovskiy
fonte
2
omg obrigado. a documentação sobre cpp-netlib é esparsa. Você tem links para boas folhas de cola?
user249806
8

Normalmente, adicionar '%' ao valor int de um char não funcionará durante a codificação, o valor deve ser o equivalente hexadecimal. por exemplo, '/' é '% 2F' e não '% 47'.

Eu acho que esta é a solução melhor e concisa para codificação e decodificação de url (sem muitas dependências de cabeçalho).

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;
}
tormuto
fonte
if(ic < 16) new_str += "%0"; Para que serve este catering ?? @tormuto @reliasn
KriyenKP
1
@Kriyen é usado para preencher o HEX codificado com zero à esquerda no caso de resultar em uma única letra; já que 0 a 15 em HEX é 0 a F.
tormuto 01 de
1
Eu gosto mais dessa abordagem. 1 para usar bibliotecas padrão. Embora haja dois problemas a serem corrigidos. Sou checo e usei a letra "ý". O resultado foi "% 0FFFFFFC3% 0FFFFFFBD". Usar primeiro a opção 16 não é necessário, pois o utf8 garante iniciar todos os bytes finais com 10 e pareceu falhar no meu multibyte. O segundo problema é o FF porque nem todos os computadores têm a mesma quantidade de bits por int. A correção foi pular a opção 16 (não necessária) e pegar os dois últimos caracteres do buffer. (Eu usei stringstream porque me sinto mais confortável com um buffer de string). Ainda deu o ponto. Como o quadro também
Volt
@Volt, você poderia postar seu código atualizado em uma nova resposta? Você menciona os problemas, mas não são informações suficientes para uma solução óbvia.
gregn3
Esta resposta tem alguns problemas, porque está usando strlen. Primeiro, isso não faz sentido, porque já sabemos o tamanho de um objeto string, então é uma perda de tempo. Muito pior, porém, é que uma string pode conter 0 bytes, que se perderiam por causa do strlen. Além disso, o if (i <16) é ineficaz, porque pode ser coberto pelo próprio printf usando "%%% 02X". E, finalmente, c deve ser um byte sem sinal, caso contrário, você obterá o efeito que @Volt estava descrevendo com '0xFFF ...' inicial.
Devolus de
8

[Modo Necromante
ativado ] Encontrei esta questão quando estava procurando uma solução rápida, moderna, independente de plataforma e elegante. Não gostou de nada acima, cpp-netlib seria o vencedor, mas tem uma vulnerabilidade de memória horrível na função "decodificada". Então eu vim com a solução de qi / karma espiritual do 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;
};

O uso do acima como segue:

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

[Modo necromante desligado]

EDIT01: consertou o zero padding - agradecimento especial a Hartmut Kaiser
EDIT02: Live on CoLiRu

Kreuzerkrieg
fonte
Qual é a “horrível vulnerabilidade de memória” de cpp-netlib? Você pode fornecer uma breve explicação ou um link?
Craig M. Brandenburg
Ele (o problema) já foi relatado, então eu não relatei e na verdade não me lembro ... algo como violação de acesso ao tentar analisar uma sequência de escape inválida ou algo assim
kreuzerkrieg
Obrigado por esclarecer!
Craig M. Brandenburg
6

CGICC inclui métodos para fazer a codificação e decodificação de url. form_urlencode e form_urldecode

alanc10n
fonte
você acabou de iniciar uma conversa decente em nosso escritório com aquela biblioteca.
JJ
1
Este é, na verdade, o código mais simples e correto.
xryl669
6

Inspirado pelo xperroni, escrevi um decodificador. Obrigado pelo ponteiro.

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

editar: cctype desnecessário removido e inclui iomainip.

kometen
fonte
1
O bloco "if (c == '%')" precisa de mais verificação fora do limite, i [1] e / ou i [2] podem estar além de text.end (). Eu mudaria o nome de "escapou" para "sem escape" também. "escaped.fill ('0');" provavelmente é desnecessário.
roalz
Por favor, olhe minha versão. Está mais otimizado. pastebin.com/g0zMLpsj
KoD de
4

Adicionando um seguimento à recomendação de Bill para usar libcurl: ótima sugestão, e para ser atualizado:
após 3 anos, a função curl_escape está obsoleta, então para uso futuro é melhor usar curl_easy_escape .

Bagelzone Ha'bonè
fonte
4

Acabei com esta questão ao procurar uma API para decodificar url em um aplicativo win32 c ++. Já que a questão não especifica a plataforma, presumir que o Windows não é uma coisa ruim.

InternetCanonicalizeUrl é a API para programas do Windows. Mais informações aqui

        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 ( aqui ) também parece ter sinalizadores para especificar se deve decodificar url

moonlightdock
fonte
3

Não consegui encontrar um URI decodificar / unescape aqui que também decodifica sequências de 2 e 3 bytes. Contribuindo com minha própria versão de alto desempenho, que converte instantaneamente a entrada c sting em 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;
}
jamacoe
fonte
#define __x2d2__(s) (__x2d__(s) << 4 | __x2d__(s+1))e deve ser construído com -WError.
Janek Olszak
Desculpe, mas "alto desempenho" ao adicionar caracteres únicos a um não wstringé realista. reserveEspaço suficiente, pelo menos , caso contrário, você terá realocações massivas o tempo todo
Felix Dombek
3

A API do Windows possui as funções UrlEscape / UrlUnescape , exportadas por shlwapi.dll, para esta tarefa.

deltanina
fonte
observação: UrlEscape não codifica+
Orwellophile
1

Esta versão é C puro e pode opcionalmente normalizar o caminho do recurso. Usá-lo com C ++ é trivial:

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

Saídas:

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

E a função real:

#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 <[email protected]>
 * @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;
}
João
fonte
Obrigado. Aqui está, sem o material de caminho opcional. pastebin.com/RN5g7g9u
Juliano
Isto não segue nenhuma recomendação e está completamente errado em comparação com o que o autor pede ('+' não é substituído por espaço, por exemplo). A normalização do caminho não tem nada a ver com a decodificação de url. Se você pretende normalizar seu caminho, você deve primeiro dividir seu URL em partes (esquema, autoridade, caminho, consulta, fragmento) e, em seguida, aplicar qualquer algoritmo que desejar apenas na parte do caminho.
xryl669
1

os pedaços suculentos

#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];
}

notar que

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

como em

// %7B = '{'

char d = from_hex('7') << 4 | from_hex('B');
Gabe Rainbow
fonte
1

Você pode usar a função "g_uri_escape_string ()" fornecida glib.h. 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;
}

compile-o com:

gcc encoding_URI.c `pkg-config --cflags --libs glib-2.0`
Vineet Mimrot
fonte
0

Eu sei que a pergunta pede um método C ++, mas para aqueles que podem precisar, eu vim com uma função muito curta em C simples para codificar uma string. Ele não cria uma nova string, ao invés disso, altera a existente, o que significa que deve ter tamanho suficiente para conter a nova string. Muito fácil de acompanhar.

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;
    }
}
Alfredo Meraz
fonte
0

você pode simplesmente usar a função AtlEscapeUrl () de atlutil.h, basta percorrer sua documentação sobre como usá-lo.

Pratik
fonte
1
isso só funcionaria no Windows
Kritzikratzi
Sim, eu tentei isso no windows.
Pratik
-2

Tive que fazer isso em um projeto sem Boost. Então, acabei escrevendo o meu próprio. Vou apenas colocá-lo no GitHub: https://github.com/corporateshark/LUrlParser

clParseURL URL = clParseURL::ParseURL( "https://name:[email protected]: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;
}
Sergey K.
fonte
Seu link é para uma biblioteca que analisa um URL. Ele não% -encode um URL. (Ou, pelo menos, não consegui ver% em nenhum lugar da fonte.) Como tal, não acho que isso responda à pergunta.
Martin Bonner apoia Monica de