Codifica / decodifica URL in C ++ [chiuso]


Risposte:


83

L'altro giorno ho affrontato la metà di codifica di questo problema. Insoddisfatto delle opzioni disponibili e dopo aver dato un'occhiata a questo codice di esempio C , ho deciso di eseguire il rollio della mia funzione di codifica URL 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();
}

L'implementazione della funzione di decodifica è lasciata come esercizio al lettore. : P


1
Credo che sia più generico (più generalmente corretto) sostituire "" con "% 20". Ho aggiornato il codice di conseguenza; sentiti libero di tornare indietro se non sei d'accordo.
Josh Kelley

1
No, sono d'accordo. setw(0)Ho anche colto l'occasione per rimuovere quella chiamata inutile (al momento pensavo che la larghezza minima sarebbe rimasta impostata fino a quando non l'avessi cambiata di nuovo, ma in realtà viene ripristinata dopo il successivo input).
xperroni

1
Ho dovuto aggiungere std :: uppercase alla riga "escaped << '%' << std :: uppercase << std :: setw (2) << int ((unsigned char) c);" Nel caso in cui altre persone si chiedano perché questo restituisca ad esempio% 3a invece di% 3A
gumlym

2
Sembra sbagliato perché le stringhe UTF-8 non sono supportate ( w3schools.com/tags/ref_urlencode.asp ). Sembra funzionare solo per Windows-1252
Skywalker13

1
Il problema era solo isalnum(c)che doveva essere cambiato inisalnum((unsigned char) c)
Skywalker13

76

Rispondendo alla mia domanda ...

libcurl ha curl_easy_escape per la codifica.

Per la decodifica, curl_easy_unescape


4
Dovresti accettare questa risposta in modo che sia mostrata in alto (e le persone possano trovarla più facile).
Mouagip

devi usare curl perché funzioni e
devi

Domanda correlata: perché unescape di curl non gestisce la modifica di "+" in spazio? Non è quella procedura standard quando si decodifica l'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);
}

non il massimo, ma funziona bene ;-)


5
Ovviamente dovresti usare al '%'posto di 37.
John Zwinck

4
Questo non converte "+" in spazio
xryl669

11

cpp-netlib ha funzioni

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

consentono di codificare e decodificare le stringhe URL molto facilmente.


2
omg grazie. la documentazione su cpp-netlib è scarsa. Hai dei link a buoni cheat sheet?
user249806

8

Normalmente l'aggiunta di '%' al valore int di un carattere non funzionerà durante la codifica, il valore dovrebbe essere l'equivalente esadecimale. ad esempio "/" è "% 2F" non "% 47".

Penso che questa sia la soluzione migliore e concisa sia per la codifica e la decodifica dell'URL (nessuna dipendenza dall'intestazione).

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"; A cosa serve questo catering ?? @tormuto @reliasn
KriyenKP

1
@Kriyen è usato per riempire l'HEX codificato con lo zero iniziale nel caso in cui risulti in una singola lettera; da 0 a 15 in HEX è da 0 a F.
tormuto

1
Mi piace di più questo approccio. +1 per l'utilizzo delle librerie standard. Sebbene ci siano due problemi da risolvere. Sono ceco e ho usato la lettera "ý". Il risultato era "% 0FFFFFFC3% 0FFFFFFBD". Il primo utilizzo dello switch 16 non è necessario poiché utf8 garantisce di avviare tutti i byte finali con 10 e sembrava fallire il mio multibyte. Il secondo problema è l'FF perché non tutti i computer hanno la stessa quantità di bit per int. La soluzione consisteva nel saltare l'opzione 16 (non necessaria) e nel prendere gli ultimi due caratteri dal buffer. (Ho usato stringstream poiché mi sento più a mio agio con e un buffer di stringa). Ancora ha dato il punto. Come anche il telaio
Volt

@Volt saresti in grado di inserire il tuo codice aggiornato in una nuova risposta? Hai menzionato i problemi ma non ci sono informazioni sufficienti per una soluzione ovvia.
gregn3

Questa risposta ha alcuni problemi, perché usa strlen. Primo, questo non ha senso, perché conosciamo già la dimensione di un oggetto stringa, quindi è una perdita di tempo. Molto peggio però è che una stringa può contenere 0 byte, che andrebbero persi a causa dello strlen. Anche if (i <16) è inefficace, perché questo può essere coperto da printf stesso usando "%%% 02X". E infine c dovrebbe essere un byte senza segno, altrimenti si ottiene l'effetto che @Volt stava descrivendo con "0xFFF ..." iniziale.
Devolus

8

[Modalità Negromante attiva]
Mi sono imbattuto in questa domanda quando stavo cercando una soluzione veloce, moderna, indipendente dalla piattaforma ed elegante. Non come nessuno dei precedenti, cpp-netlib sarebbe il vincitore ma ha un'orribile vulnerabilità di memoria nella funzione "decodificato". Quindi ho escogitato la soluzione di qi / karma spirituale di 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;
};

L'uso di sopra come 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;
}

[Modalità negromante disattivata]

EDIT01: risolto il problema con zero padding - un ringraziamento speciale a Hartmut Kaiser
EDIT02: Live on CoLiRu


Qual è l '"orribile vulnerabilità della memoria" cpp-netlib? Potete fornire una breve spiegazione o un collegamento?
Craig M. Brandenburg

(Il problema) era già stato segnalato, quindi non l'ho segnalato e in realtà non ricordo ... qualcosa come la violazione di accesso durante il tentativo di analizzare una sequenza di escape non valida, o qualcosa del genere
kreuzerkrieg


Grazie per il chiarimento!
Craig M. Brandenburg


6

Ispirato da xperroni ho scritto un decoder. Grazie per il puntatore.

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

modifica: rimossi cctype e iomainip inclusi.


1
Il blocco "if (c == '%')" necessita di un maggiore controllo out-of-bound, i [1] e / o i [2] potrebbero essere oltre text.end (). Rinominerei anche "escaped" in "unescaped". "escaped.fill ('0');" probabilmente non è necessario.
roalz

Per favore, guarda la mia versione. È più ottimizzato. pastebin.com/g0zMLpsj
KoD


4

Sono finito su questa domanda durante la ricerca di un'API per decodificare l'URL in un'app Win32 C ++. Dal momento che la domanda non specifica la piattaforma presumendo che Windows non sia una brutta cosa.

InternetCanonicalizeUrl è l'API per i programmi Windows. Maggiori info qui

        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
        }

Anche InternetCrackUrl ( qui ) sembra avere flag per specificare se decodificare l'URL


3

Non sono riuscito a trovare un URI decodifica / unescape qui che decodifica anche sequenze di 2 e 3 byte. Contribuendo alla mia versione ad alte prestazioni, che al volo converte l'input di c sting in una 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))e verrà compilato con -WError.
Janek Olszak

Siamo spiacenti ma "prestazioni elevate" durante l'aggiunta di singoli caratteri a a wstringnon è realistico. Almeno reserveabbastanza spazio, altrimenti avrai sempre enormi riallocazioni
Felix Dombek


1

Questa versione è C puro e può facoltativamente normalizzare il percorso della risorsa. Usarlo con C ++ è banale:

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

Uscite:

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

E la funzione effettiva:

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

Grazie. Qui è senza la roba del percorso opzionale. pastebin.com/RN5g7g9u
Julian

Questo non segue alcuna raccomandazione ed è completamente sbagliato rispetto a quanto richiesto dall'autore ("+" non è sostituito da uno spazio per esempio). La normalizzazione del percorso non ha nulla a che fare con la decodifica dell'URL. Se intendi normalizzare il tuo percorso, dovresti prima dividere il tuo URL in parti (schema, autorità, percorso, query, frammento) e poi applicare qualsiasi algoritmo che ti piace solo sulla parte del percorso.
xryl669

1

i pezzi succosi

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

notandolo

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

come in

// %7B = '{'

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

1

È possibile utilizzare la funzione "g_uri_escape_string ()" fornita 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;
}

compilarlo con:

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


0

So che la domanda richiede un metodo C ++, ma per coloro che potrebbero averne bisogno, ho ideato una funzione molto breve in C semplice per codificare una stringa. Non crea una nuova stringa, piuttosto altera quella esistente, il che significa che deve avere una dimensione sufficiente per contenere la nuova stringa. Molto facile tenere il passo.

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

puoi semplicemente usare la funzione AtlEscapeUrl () da atlutil.h, basta passare attraverso la sua documentazione su come usarlo.


1
questo funzionerebbe solo su Windows
kritzikratzi

Sì, l'ho provato su Windows.
Pratik

-2

Ho dovuto farlo in un progetto senza Boost. Quindi, ho finito per scrivere il mio. Lo metterò su 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;
}

Il tuo collegamento è a una libreria che analizza un URL. Non codifica% un URL. (O almeno, non sono riuscito a vedere una% da nessuna parte nella sorgente.) Come tale, non credo che questo risponda alla domanda.
Martin Bonner supporta Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.