Koduj / dekoduj adresy URL w C ++ [zamknięte]

Odpowiedzi:

81

Niedawno miałem do czynienia z połową tego problemu z kodowaniem. Niezadowolony z dostępnych opcji i po przyjrzeniu się temu przykładowemu kodowi w C , zdecydowałem się wypuścić własną funkcję kodowania url w 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();
}

Realizacja funkcji dekodowania jest pozostawiona czytelnikowi jako ćwiczenie. : P

xperroni
źródło
1
Uważam, że bardziej ogólne (bardziej ogólnie poprawne) jest zastąpienie „” przez „% 20”. Odpowiednio zaktualizowałem kod; Jeśli się nie zgadzasz, możesz się wycofać.
Josh Kelley,
1
Nie, zgadzam się. Skorzystałem również z okazji, aby usunąć to bezcelowe setw(0)wywołanie (wtedy myślałem, że minimalna szerokość pozostanie ustawiona, dopóki nie zmienię jej z powrotem, ale w rzeczywistości jest resetowana po następnym wprowadzeniu).
xperroni
1
Musiałem dodać std :: uppercase do linii "escaped << '%' << std :: uppercase << std :: setw (2) << int ((unsigned char) c);" Na wypadek, gdyby inni zastanawiali się, dlaczego to wraca na przykład% 3a zamiast% 3A
gumlym
2
Wygląda nieprawidłowo, ponieważ ciągi znaków UTF-8 nie są obsługiwane ( w3schools.com/tags/ref_urlencode.asp ). Wygląda na to, że działa tylko na Windows-1252
Skywalker13
1
Problem polegał na tym isalnum(c), że trzeba to zmienić naisalnum((unsigned char) c)
Skywalker13
74

Odpowiadając na moje własne pytanie ...

libcurl ma curl_easy_escape do kodowania.

Do dekodowania curl_easy_unescape

user126593
źródło
4
Powinieneś zaakceptować tę odpowiedź, aby była pokazana u góry (i ludzie mogą to łatwiej znaleźć).
Mouagip
musisz użyć curl, aby to zadziałało i musisz uwolnić pamięć
xinthose
Powiązane pytanie: dlaczego unescape curl nie obsługuje zmiany znaku „+” na spację? Czy nie jest to standardowa procedura podczas dekodowania adresu 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);
}

nie najlepszy, ale działa dobrze ;-)


źródło
5
Oczywiście powinieneś użyć '%'zamiast 37.
John Zwinck
4
To nie konwertuje „+” na spację
xryl669
11

cpp-netlib ma funkcje

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

umożliwiają bardzo łatwe kodowanie i dekodowanie ciągów adresów URL.

Yuriy Petrovskiy
źródło
2
omg dziękuję. dokumentacja dotycząca cpp-netlib jest rzadka. Czy masz jakieś linki do dobrych ściągawek?
user249806
8

Zwykle dodanie „%” do wartości int znaku nie zadziała podczas kodowania, wartość ma być odpowiednikiem szesnastkowym. np. „/” to „% 2F”, a nie „% 47”.

Myślę, że jest to najlepsze i zwięzłe rozwiązanie zarówno dla kodowania, jak i dekodowania adresów URL (Brak wielu zależności między nagłówkami).

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
źródło
if(ic < 16) new_str += "%0"; Po co to catering? @tormuto @reliasn
KriyenKP
1
@Kriyen służy do dopełnienia zakodowanego szesnastkowego zera wiodącego w przypadku, gdy wynikiem jest pojedyncza litera; od 0 do 15 w
formacie
1
Najbardziej podoba mi się to podejście. +1 za używanie bibliotek standardowych. Chociaż są dwa problemy do naprawienia. Jestem Czechem i użyłem litery „ý”. Wynik to „% 0FFFFFFC3% 0FFFFFFBD”. Najpierw użycie przełącznika 16 nie jest konieczne, ponieważ utf8 gwarantuje rozpoczęcie wszystkich końcowych bajtów od 10 i wydawało się, że zawiódł mój wielobajt. Drugą kwestią jest FF, ponieważ nie wszystkie komputery mają taką samą liczbę bitów na int. Rozwiązaniem było pominięcie przełącznika 16 (niepotrzebne) i pobranie ostatnich dwóch znaków z bufora. (Użyłem stringstream, ponieważ czuję się bardziej komfortowo z buforem struny). Nadal dawał rację. Podobnie jak ramka
Volt
@Volt czy mógłbyś zamieścić zaktualizowany kod w nowej odpowiedzi? Wspominasz o problemach, ale to nie są wystarczające informacje do oczywistej poprawki.
gregn3
Ta odpowiedź ma pewne problemy, ponieważ używa strlen. Po pierwsze, nie ma to sensu, ponieważ znamy już rozmiar obiektu typu string, więc jest to strata czasu. Znacznie gorsze jest jednak to, że łańcuch może zawierać 0 bajtów, które zostałyby utracone z powodu strlen. Również if (i <16) jest nieefektywne, ponieważ może to zostać pokryte przez printf za pomocą „%%% 02X”. I na koniec c powinno być bajtem bez znaku, w przeciwnym razie otrzymasz efekt, który opisywał @Volt z początkowym „0xFFF ...”.
Devolus,
8

[Tryb nekromanty włączony]
Natknąłem się na to pytanie, gdy szukałem szybkiego, nowoczesnego, niezależnego od platformy i eleganckiego rozwiązania. Nie podobał się żaden z powyższych, cpp-netlib byłby zwycięzcą, ale ma przerażającą lukę w pamięci w funkcji "dekodowanej". Więc wymyśliłem rozwiązanie Qi / Karmy ducha 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;
};

Wykorzystanie powyższego w następujący sposób:

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

[Tryb nekromanty wyłączony]

EDIT01: poprawiono zerową wyściółkę - specjalne podziękowania dla Hartmuta Kaisera
EDIT02: Live on CoLiRu

kreuzerkrieg
źródło
Co to za „przerażająca luka w zabezpieczeniach pamięci” cpp-netlib? Czy możesz podać krótkie wyjaśnienie lub link?
Craig M. Brandenburg
To (problem) został już zgłoszony, więc nie zgłosiłem i właściwie nie pamiętam ... coś takiego jak naruszenie dostępu podczas próby parsowania nieprawidłowej sekwencji ucieczki, czy coś
kreuzerkrieg.
och, proszę bardzo github.com/cpp-netlib/cpp-netlib/issues/501
kreuzerkrieg.
Dzięki za wytłumaczenie!
Craig M. Brandenburg
6

CGICC zawiera metody do kodowania i dekodowania adresów URL. form_urlencode i form_urldecode

alanc10n
źródło
właśnie wywołałeś przyzwoitą rozmowę w naszym biurze z tą biblioteką.
JJ
1
W rzeczywistości jest to najprostszy i najbardziej poprawny kod.
xryl669
6

Zainspirowany xperroni napisałem dekoder. Dziękuję za wskazówkę.

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

edit: Usunięto niepotrzebne pliki cctype i iomainip.

kometen
źródło
1
Blok "if (c == '%')" wymaga więcej sprawdzania poza zakresem, i [1] i / lub i [2] mogą znajdować się poza text.end (). Zmieniłbym też nazwę „uciekł” na „bez ucieczki”. "escaped.fill ('0');" jest prawdopodobnie niepotrzebne.
roalz
Proszę, spójrz na moją wersję. Jest bardziej zoptymalizowany. pastebin.com/g0zMLpsj
KoD
4

Dodanie uzupełnienia do zalecenia Billa dotyczącego używania libcurl: świetna sugestia i do zaktualizowania:
po 3 latach funkcja curl_escape jest przestarzała, więc do przyszłego użytku lepiej jest użyć curl_easy_escape .

Bagelzone Ha'bonè
źródło
4

Skończyło się na tym pytaniu, kiedy szukałem interfejsu API do dekodowania adresu URL w aplikacji win32 c ++. Ponieważ pytanie nie do końca określa platformę, zakładając, że okna nie są złe.

InternetCanonicalizeUrl to API dla programów systemu Windows. Więcej informacji tutaj

        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
        }

Wydaje się, że InternetCrackUrl ( tutaj ) również ma flagi określające, czy dekodować adres URL

Moonlightdock
źródło
3

Nie mogłem znaleźć tutaj dekodowania / unescape URI, który dekoduje również sekwencje 2 i 3 bajtowe. Udostępniając swoją własną wersję o wysokiej wydajności, która w locie konwertuje dane wejściowe do 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
źródło
#define __x2d2__(s) (__x2d__(s) << 4 | __x2d__(s+1))i będzie budować z -WError.
Janek Olszak
Przepraszamy, ale „wysoka wydajność” podczas dodawania pojedynczych znaków do a wstringjest nierealne. Przynajmniej reservewystarczająco dużo miejsca, w przeciwnym razie będziesz mieć ogromne realokacje przez cały czas
Felix Dombek
3

Interfejs API systemu Windows zawiera funkcje UrlEscape / UrlUnescape , wyeksportowane przez shlwapi.dll, do tego zadania.

deltanina
źródło
uwaga: UrlEscape nie koduje+
Orwellophile
1

Ta wersja jest czystym C i może opcjonalnie znormalizować ścieżkę zasobów. Używanie go z C ++ jest trywialne:

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

Wyjścia:

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

A rzeczywista funkcja:

#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;
}
Johan
źródło
Dzięki. Tutaj jest bez opcjonalnej ścieżki. pastebin.com/RN5g7g9u
Julian
Nie wynika to z żadnych zaleceń i jest całkowicie błędne w porównaniu z tym, o co prosi autor (na przykład „+” nie jest zastępowane spacją). Normalizacja ścieżki nie ma nic wspólnego z dekodowaniem adresów URL. Jeśli zamierzasz znormalizować swoją ścieżkę, powinieneś najpierw podzielić swój adres URL na części (schemat, autorytet, ścieżka, zapytanie, fragment), a następnie zastosować dowolny algorytm tylko w części ścieżki.
xryl669
1

soczyste kawałki

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

zauważając to

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

jak w

// %7B = '{'

char d = from_hex('7') << 4 | from_hex('B');
Gabe Rainbow
źródło
1

Możesz użyć funkcji "g_uri_escape_string ()" udostępnionej w 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;
}

skompiluj go z:

gcc encoding_URI.c `pkg-config --cflags --libs glib-2.0`
Vineet Mimrot
źródło
0

Wiem, że pytanie dotyczy metody C ++, ale dla tych, którzy mogą jej potrzebować, wymyśliłem bardzo krótką funkcję w zwykłym C do zakodowania ciągu. Nie tworzy nowego ciągu, a raczej zmienia istniejący, co oznacza, że ​​musi mieć wystarczający rozmiar, aby pomieścić nowy ciąg. Bardzo łatwo nadążyć.

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
źródło
0

możesz po prostu użyć funkcji AtlEscapeUrl () z atlutil.h, po prostu przejrzyj jej dokumentację, jak z niej korzystać.

Pratik
źródło
1
to działałoby tylko w oknach
kritzikratzi
Tak, próbowałem tego w systemie Windows.
Pratik
-2

Musiałem to zrobić w projekcie bez Boost. Więc skończyło się na pisaniu własnego. Po prostu umieszczę to na 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;
}
Siergiej K.
źródło
Twój link prowadzi do biblioteki, która analizuje adres URL. Nie% -koduje adresu URL. (A przynajmniej nigdzie nie widziałem% w źródle). W związku z tym nie sądzę, aby to odpowiadało na pytanie.
Martin Bonner wspiera Monikę