FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index All products support UTF-8-Sicherheitsfunktion (SAFEUTF8, CP1252TOUTF8, ISVALIDUTF8)
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
UTF-8-Sicherheitsfunktion (SAFEUTF8, CP1252TOUTF8, ISVALIDUTF8)
Posted: Mon Sep 08, 2025 01:52 PM
************************************************************
Hinweis: Der folgende Abschnitt wurde mit UnterstĂĽtzung von ChatGPT erstellt,
von mir jedoch getestet und erfolgreich im Einsatz.

Note: The following section was generated with the assistance of ChatGPT,
but has already been tested and is successfully in use.
************************************************************

Zur zuverlässigen Übergabe von DBF-Daten an den PHP-Proxy wird im Microservice eine C-Funktion per #pragma BEGINDUMP eingebunden. Hintergrund: Viele ältere DBF-Dateien sind in DEWIN/Windows-1252 codiert und enthalten teilweise Steuerzeichen im Bereich 0x00–0x1F oder Sonderzeichen 0x80–0x9F (z. B. €, „, …). Diese Bytes sind in UTF-8 nicht gültig und führen beim hb_jsonEncode() auf der PHP-Seite zu „Bad JSON“-Fehlern.

Die drei bereitgestellten Funktionen:

ISVALIDUTF8( cText ) -> lOk
PrĂĽft, ob der ĂĽbergebene String bereits korrekt UTF-8-codiert ist (.T. oder .F.).

CP1252TOUTF8( cText ) -> cUtf8
Wandelt einen 8-Bit-String im Zeichensatz Windows-1252/DEWIN in gĂĽltiges UTF-8 um.

SAFEUTF8( cText ) -> cUtf8
Kombiniert beide Schritte:

Entfernt unzulässige Steuerzeichen (< 32, außer TAB/CR/LF).

Erkennt, ob die Eingabe schon UTF-8 ist. Falls nein, konvertiert sie von CP1252/ISO-8859-1 nach UTF-8.

Führt ein Mapping der problematischen 0x80–0x9F-Zeichen durch (€, „…“, —, ™ usw.).

Gibt garantiert valide UTF-8 zurĂĽck, sodass JSON-Serialisierung nicht mehr abbricht.

Performance:
Die C-Implementierung arbeitet in einem einzigen Durchlauf über die Bytes (O(n)), mit kleinem Lookup-Array. Dadurch ist die Geschwindigkeit im Bereich von hunderten MB/s und für typische DBF-Feldlängen praktisch ohne spürbare Mehrbelastung.

Integration:

Die Funktionen werden in einer .prg-Datei eingebunden (#pragma BEGINDUMP).

In den DBF-Leseroutinen wird bei allen CHAR-Feldern vor dem hb_jsonEncode() ein Aufruf SAFEUTF8( AllTrim( cValue ) ) gemacht.

So ist sichergestellt, dass die erzeugte JSON-Antwort auch bei alten Datenbeständen mit Umlauten oder Steuerzeichen immer korrekt und ohne Timeout/Decode-Fehler beim PHP-Proxy ankommt.

#pragma BEGINDUMP
/* C89, Harbour, BCC7-kompatibel */

#include "hbapi.h"
#include <string.h>

/* ---------- Prototypen (gegen W8065) ---------- */
static int  is_valid_utf8( const unsigned char *s, long n );
static int  utf8_emit( unsigned char *dst, unsigned long cp );
static int  is_bad_ctrl( unsigned char c );
static void cp1252_to_utf8( const unsigned char *src, long n,
                            unsigned char **outBuf, long *outLen );

/* ---------- Globale Map (verhindert E2451 cp1252_map) ---------- */
/* Windows-1252: 0x80–0x9F → Unicode Codepoints */
static unsigned short cp1252_map[32] = {
   0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
   0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017D, 0x0000,
   0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
   0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0000, 0x017E, 0x0178
};

/* ---------- Implementierungen ---------- */

static int is_valid_utf8( const unsigned char *s, long n )
{
   long i = 0;
   while( i < n )
   {
      unsigned char c = s[i];
      if( c < 0x80 )
      {
         i++;
      }
      else if( c >= 0xC2 && c <= 0xDF )
      {
         if( i + 1 >= n ) return 0;
         if( ( s[i+1] & 0xC0 ) != 0x80 ) return 0;
         i += 2;
      }
      else if( c >= 0xE0 && c <= 0xEF )
      {
         if( i + 2 >= n ) return 0;
         if( ( s[i+1] & 0xC0 ) != 0x80 || ( s[i+2] & 0xC0 ) != 0x80 ) return 0;
         i += 3;
      }
      else if( c >= 0xF0 && c <= 0xF4 )
      {
         if( i + 3 >= n ) return 0;
         if( ( s[i+1] & 0xC0 ) != 0x80 || ( s[i+2] & 0xC0 ) != 0x80 || ( s[i+3] & 0xC0 ) != 0x80 ) return 0;
         i += 4;
      }
      else
         return 0;
   }
   return 1;
}

/* Unicode codepoint → UTF-8 */
static int utf8_emit( unsigned char *dst, unsigned long cp )
{
   if( cp <= 0x7F ) {
      dst[0] = (unsigned char) cp; return 1;
   } else if( cp <= 0x7FF ) {
      dst[0] = 0xC0 | ( cp >> 6 );
      dst[1] = 0x80 | ( cp & 0x3F );
      return 2;
   } else if( cp <= 0xFFFF ) {
      dst[0] = 0xE0 | ( cp >> 12 );
      dst[1] = 0x80 | ( ( cp >> 6 ) & 0x3F );
      dst[2] = 0x80 | ( cp & 0x3F );
      return 3;
   } else {
      dst[0] = 0xF0 | ( cp >> 18 );
      dst[1] = 0x80 | ( ( cp >> 12 ) & 0x3F );
      dst[2] = 0x80 | ( ( cp >> 6 ) & 0x3F );
      dst[3] = 0x80 | ( cp & 0x3F );
      return 4;
   }
}

/* harte Steuerzeichen (<32) auĂźer TAB/LF/CR entfernen */
static int is_bad_ctrl( unsigned char c )
{
   return ( c < 32 ) && !( c == 9 || c == 10 || c == 13 );
}

/* Kernpfad: CP1252 → UTF-8 (mit Control-Strip). Worst case 3x Größe reicht. */
static void cp1252_to_utf8( const unsigned char *src, long n,
                            unsigned char **outBuf, long *outLen )
{
   long i, j = 0;
   unsigned char *dst;

   if( n < 0 ) n = 0;
   dst = (unsigned char *) hb_xgrab( (HB_SIZE) ( n * 3 + 1 ) );

   for( i = 0; i < n; ++i )
   {
      unsigned char c = src[i];
      if( is_bad_ctrl( c ) )
      {
         dst[j++] = ' ';
         continue;
      }

      if( c < 0x80 )
      {
         dst[j++] = c;
      }
      else if( c >= 0x80 && c <= 0x9F )
      {
         unsigned short u = cp1252_map[ c - 0x80 ];
         if( u == 0 )
            dst[j++] = '?';
         else
            j += utf8_emit( dst + j, (unsigned long) u );
      }
      else
      {
         /* 0xA0–0xFF → U+00A0..U+00FF */
         j += utf8_emit( dst + j, (unsigned long) c );
      }
   }

   dst[j] = '\0';
   *outBuf = dst;
   *outLen = j;
}

/* ---------- Harbour-Exports ---------- */

HB_FUNC( ISVALIDUTF8 )
{
   const char *p = hb_parc( 1 );
   long n = hb_parclen( 1 );
   if( p == NULL ) { hb_retl( 1 ); return; }
   hb_retl( is_valid_utf8( (const unsigned char *) p, n ) );
}

HB_FUNC( CP1252TOUTF8 )
{
   const unsigned char *p = (const unsigned char *) hb_parc( 1 );
   long n = hb_parclen( 1 );
   unsigned char *out = NULL;
   long outLen = 0;

   if( p == NULL || n <= 0 ) { hb_retclen( "", 0 ); return; }

   cp1252_to_utf8( p, n, &out, &outLen );
   /* Harbour ĂĽbernimmt das Freeing des Buffers */
   hb_retclen_buffer( (char *) out, (HB_SIZE) outLen );
}

HB_FUNC( SAFEUTF8 )
{
   const unsigned char *p = (const unsigned char *) hb_parc( 1 );
   long n = hb_parclen( 1 );

   if( p == NULL || n <= 0 ) { hb_retclen( "", 0 ); return; }

   if( is_valid_utf8( p, n ) )
   {
      /* Zero-copy fast path, falls keine harten Steuerzeichen */
      long i;
      for( i = 0; i < n; ++i )
      {
         unsigned char c = p[i];
         if( is_bad_ctrl( c ) )
         {
            /* kopierende Variante mit Strip */
            long k, j = 0;
            unsigned char *dst = (unsigned char *) hb_xgrab( (HB_SIZE) n + 1 );
            for( k = 0; k < n; ++k )
            {
               unsigned char d = p[k];
               dst[j++] = is_bad_ctrl( d ) ? ' ' : d;
            }
            dst[ j ] = '\0';
            hb_retclen_buffer( (char *) dst, (HB_SIZE) j );
            return;
         }
      }
      /* keine Bad-Controls → Original zurückgeben */
      hb_retclen( (const char *) p, (HB_SIZE) n );
   }
   else
   {
      unsigned char *out = NULL; long outLen = 0;
      cp1252_to_utf8( p, n, &out, &outLen );
      hb_retclen_buffer( (char *) out, (HB_SIZE) outLen );
   }
}
#pragma ENDDUMP

Continue the discussion