FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour return json hix / mod_harbour
Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
return json hix / mod_harbour
Posted: Wed Feb 04, 2026 05:11 PM

Buena tarde...

Requiero retornar un json en una consulta via http / get

PROCEDURE Main()

     local aH := {=>}

aH[ 'time' ] 	= time()
aH[ 'date' ] 	= date()
aH[ 'age'  ] 	= 123
aH[ 'married' ] = .T.

Header( "Access-Control-Allow-Origin: http://localhost:4200" )
Header( "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS" )
Header( "Access-Control-Allow-Headers: Content-Type, Authorization" )
header( "Content-Type: application/json" )

?? hb_jsonEncode( aH )

return nil

error :
Access to fetch at 'http://localhost/test.prg' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested

Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 05:54 PM

In Hix/mod_harbour, HTTP headers are set via the response object, not printed.
CGI-style solutions using hb_header() or text output do not apply.

not tested:

PROCEDURE Main( oRequest, oResponse )

   oResponse:SetHeader( "Access-Control-Allow-Origin", "http://localhost:4200" )
   oResponse:SetHeader( "Access-Control-Allow-Methods", "GET, POST, OPTIONS" )
   oResponse:SetHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" )
   oResponse:SetHeader( "Content-Type", "application/json; charset=utf-8" )

   oResponse:Write( ;
      '{ "data": {}, "status": "success", "message": "GET korrekt ausgefĂĽhrt" }' ;
   )

RETURN
Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 06:28 PM

Error BASE/EG_NOMETHOD(13) 1004 Description Message not found Operation NIL:SETHEADER Arguments 1: NIL

Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 06:46 PM

Asi funciono en mod_harbour v2.1

pero en HIX .. No

PROCEDURE Main()

     local aH := {=>}

aH[ 'time' ] 	= time()
aH[ 'date' ] 	= date()
aH[ 'age'  ] 	= 123
aH[ 'married' ] = .T.



   Header( "Access-Control-Allow-Origin: http://localhost:4200" )
   Header( "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS" )
   Header( "Access-Control-Allow-Headers: Content-Type, Authorization" )
   header( "Content-Type: application/json" )

?? hb_jsonEncode( aH )
return nil


/// y en apache

<FilesMatch "\.(prg|hrb)$">
    SetHandler harbour
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type"
</FilesMatch>

LoadModule mod_harbourV2_module modules/mod_harbour.v2.so
MH_LIBRARY d:\\xampp\htdocs\\libmhapache.dll
MH_NVMS 50
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 06:58 PM

Jonsson, first of all, thank you for your questions. You are one of the few people here who actively contributes to web development in public.

As this discussion shows, in real-world scenarios there are often small issues caused by the many different system environments. When these are discussed openly, however, they can usually be identified and resolved very quickly.

I didn’t know this before either — you need to handle the HTTP OPTIONS method for the CORS preflight to work correctly.

Independent of the forum question, these are the key learnings that directly apply when running your own HTTP server:

JSON ≠ HTTP

JSON is just the payload. The browser decides whether a response is accessible solely based on the HTTP headers, not on the JSON content.

OPTIONS is not a separate endpoint

Before sending a GET or POST, the browser automatically sends an OPTIONS request to the same route. If this request is not handled, the actual request is never executed.

What I had to add

Handling OPTIONS by returning only CORS headers and no body:

IF oRequest:Method() == "OPTIONS" oResponse:SetHeader( "Access-Control-Allow-Origin", "*" ) oResponse:SetHeader( "Access-Control-Allow-Methods", "GET, POST, OPTIONS" ) oResponse:SetHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" ) oResponse:SetStatus( 204 ) RETURN ENDIF

Only after this did CORS requests start working for my microservice — meaning normal browser GET/POST requests returning JSON. Before that, it only worked for same-origin requests (same domain/port). Lesson learned.

Best regards, Otto

PS: At this point, this likely needs input from Charly. Hix/mod_harbour has its own execution and response model, and the correct way to access the response object depends on the internal Hix API.

Posts: 1283
Joined: Fri Feb 10, 2006 02:34 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 07:33 PM

Hola,

Code (harbour): Select all Collapse
function main()

local aH := {=>}

aH[ 'time' ] 	= time()
aH[ 'date' ] 	= date()
aH[ 'age'  ] 	= 123
aH[ 'married' ] = .T.

UAddHeader( "Content-Type", "application/json" )
UAddHeader( "Access-Control-Allow-Origin", '*')
	
retu hb_jsonEncode( aH )

Aqui tienes las funciones de HIX
https://github.com/carles9000/hix/wiki/Functions

C.

Salutacions, saludos, regards

"...programar es fácil, hacer programas es difícil..."

UT Page -> https://carles9000.github.io/
Forum UT -> https://discord.gg/bq8a9yGMWh
HIX -> https://github.com/carles9000/hix
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 08:03 PM

Jonsson — now that you’re using the correct Hix function, here are the missing CORS headers:

UAddHeader( "Access-Control-Allow-Origin", "*" ) UAddHeader( "Access-Control-Allow-Methods", "GET, POST, OPTIONS" ) UAddHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" )

For browser POST requests you also need to handle the OPTIONS preflight, otherwise the actual request will never be sent. To make CORS work reliably in the browser, a few things are still required:

JSON is only the payload — the browser decides access based on HTTP headers.

For browser POST requests (JSON, Authorization, etc.), the browser sends an OPTIONS preflight request first.

This OPTIONS request must be explicitly handled, otherwise the actual GET/POST request is never sent.

In addition to Access-Control-Allow-Origin, the following headers are needed:

UAddHeader( "Access-Control-Allow-Methods", "GET, POST, OPTIONS" ) UAddHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" )

And an OPTIONS handler that returns only these headers (no body).

Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 10:15 PM

Otto y Carles...

Garcias por sus prontas respuestas...

Ya retorna el json desde HIX como desde M_H...

Los siguientes requerimientos son :

  1. Generar el token JWT - harbour
  2. Poder cargar una dll (fivewin que Antonio está compilando .. requerimiento pendiente... )
    ( mi lĂłgica usa esa dll. para crear un Json como imagen de la consulta del query a tablas ( dbf o sql).. y asĂ­ reusarĂ­a
    gran parte de mi lĂłgica

Saludos...

Nota : frontEnd : angular (que ya está funcionando)
ahora estoy reusando este frontEnd, para migrar el app-local, que se basa en FIVEWIN / xharbour
Los backends que estoy evaluando son : HIX , M_H (dbf y sql ) y NODEJS (sql)....

que lib de sql podría usar o está disponible tanto en HIX como en M_H ( mariaDb, tdolphin, eagle u otra lib de manu, etc) ?

La idea es validar el rendimiento con los 3 server .. con un prototipo básico

Saludos

Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Wed Feb 04, 2026 10:48 PM

Jonsson, What kind of software are you actually building?
Sometimes it looks very complex from the outside, almost like building a rocket with its own launchpad.

I’m asking because I’ve often found that when the business logic already exists conceptually, it can be faster and cleaner to rebuild it with today’s tools rather than trying to carry over a very complex implementation. In the long run, that often leads to a more maintainable and future-proof system.

What advantages do you see in using Angular for this project?
And are you already fluent in Angular, or is this also part of the learning and evaluation process?

Best regards,
Otto

With mod harbour I used this JWT implementation.
Maybe it can be useful or at least serve as a reference.

/*
 * Copyright (c) 2019 Matteo Baccan
 * https://www.baccan.it
 *
 * Distributed under the GPL v3 software license, see the accompanying
 * file LICENSE or http://www.gnu.org/licenses/gpl.html.
 *
 */
/**
 * JWT Implementation
 *
 * https://datatracker.ietf.org/doc/html/rfc7519
 *
 */
#include "hbclass.ch"

CLASS JWT

HIDDEN:

  CLASSDATA cSecret
  CLASSDATA aHeader
  CLASSDATA aPayload
  CLASSDATA cError

  METHOD Base64UrlEncode( cData )
  METHOD Base64UrlDecode( cData )
  METHOD ByteToString( cData )
  METHOD GetSignature( cHeader, cPayload, cSecret, cAlgorithm )
  METHOD CheckPayload(aPayload, cKey)

EXPORTED:

  METHOD New() CONSTRUCTOR

  // Header
  METHOD SetType( cType )
  METHOD GetType()                          INLINE ::aHeader[ 'typ' ]
  METHOD SetContentType( cContentType )     INLINE ::aHeader[ 'cty' ] :=  cContentType
  METHOD GetContentType()                   INLINE ::aHeader[ 'cty' ]
  METHOD SetAlgorithm( cAlgorithm )
  METHOD GetAlgorithm()                     INLINE ::aHeader[ 'alg' ]

  // Payload
  METHOD SetIssuer( cIssuer )               INLINE ::SetPayloadData('iss', cIssuer)
  METHOD GetIssuer()                        INLINE ::GetPayloadData('iss')
  METHOD SetSubject( cSubject )             INLINE ::SetPayloadData('sub', cSubject)
  METHOD GetSubject()                       INLINE ::GetPayloadData('sub')
  METHOD SetAudience( cAudience )           INLINE ::SetPayloadData('aud', cAudience)
  METHOD GetAudience()                      INLINE ::GetPayloadData('aud')
  METHOD SetExpration( nExpiration )        INLINE ::SetPayloadData('exp', nExpiration)
  METHOD GetExpration()                     INLINE ::GetPayloadData('exp')
  METHOD SetNotBefore( nNotBefore )         INLINE ::SetPayloadData('nbf', nNotBefore)
  METHOD GetNotBefore()                     INLINE ::GetPayloadData('nbf')
  METHOD SetIssuedAt( nIssuedAt )           INLINE ::SetPayloadData('iat', nIssuedAt)
  METHOD GetIssuedAt()                      INLINE ::GetPayloadData('iat')
  METHOD SetJWTId( cJWTId )                 INLINE ::SetPayloadData('jti', cJWTId)
  METHOD GetJWTId()                         INLINE ::GetPayloadData('jti')

  // Payload methods
  METHOD SetPayloadData( cKey, uValue )     INLINE IF( uValue==NIL, hb_HDel(::aPayload,cKey), ::aPayload[cKey] := uValue)
  METHOD GetPayloadData( cKey )             INLINE IF( hb_HHasKey(::aPayLoad,cKey), ::aPayload[cKey], NIL )

  // Secret
  METHOD SetSecret( cSecret )               INLINE ::cSecret := cSecret
  METHOD GetSecret()                        INLINE ::cSecret

  // Error
  METHOD GetError()                         INLINE ::cError

  // Cleanup: aHeader, aPayload, cError, cSecret
  METHOD Reset()

  // Encode a JWT and return it
  METHOD Encode()

  // Decode a JWT
  METHOD Decode( cJWT )

  // Decode a JWT
  METHOD Verify( cJWT )

  // Getter internal data with internal exposion
  METHOD GetPayload()                       INLINE hb_hClone(::aPayload)
  METHOD GetHeader()                        INLINE hb_hClone(::aHeader)

  // Helper method for expiration setting
  METHOD GetSeconds()

  // Versione
  METHOD GetVersion()                       INLINE "1.0.2"

ENDCLASS

METHOD New() CLASS JWT
  ::Reset()
RETU SELF

// Optional
METHOD SetType( cType ) CLASS JWT
  LOCAL bRet := .F.

  IF cType=="JWT"
      ::aHeader[ 'typ' ] := cType
  ELSE
      bRet := .F.
      ::cError := "Invalid type [" +cType +"]"
  ENDIF

RETU bRet

// Mandatory
METHOD SetAlgorithm( cAlgorithm ) CLASS JWT
  LOCAL bRet := .F.

  IF cAlgorithm=="HS256" .OR. cAlgorithm=="HS384" .OR. cAlgorithm=="HS512"
      ::aHeader[ 'alg' ] := cAlgorithm
  ELSE
      bRet := .F.
      ::cError := "Invalid algorithm [" +cAlgorithm +"]"
  ENDIF

RETU bRet

METHOD Reset() CLASS JWT

  ::aHeader   := {=>}
  ::aPayload := {=>}
  ::cError  := ''
  ::cSecret  := ''

RETU NIL


METHOD Encode() CLASS JWT

  LOCAL cHeader
  LOCAL cPayload
  LOCAL cSignature

  //  Encode header
  cHeader     := ::Base64UrlEncode( hb_jsonEncode( ::aHeader ) )

  // Encode payload
  cPayload    := ::Base64UrlEncode( hb_jsonEncode( ::aPayload ) )

  //  Make signature
  cSignature := ::GetSignature( cHeader, cPayload, ::cSecret, ::aHeader[ 'alg' ] )

//  Return JWT
RETU cHeader + '.' + cPayload + '.' + cSignature

METHOD Base64UrlEncode( cData ) CLASS JWT
RETU hb_StrReplace( hb_base64Encode( cData ), "+/=", { "-", "_", "" } )

METHOD Base64UrlDecode( cData ) CLASS JWT
RETU hb_base64Decode( hb_StrReplace( cData, "-_", "+/" ) )

METHOD ByteToString( cData ) CLASS JWT
   LOCAL cRet := SPACE(LEN(cData)/2)
   LOCAL nLen := LEN( cData )
   LOCAL nX, nNum

   cData := UPPER(cData)
   FOR nX := 1 TO nLen STEP 2
      nNum := ( AT( SubStr( cData, nX  , 1 ), "0123456789ABCDEF" ) - 1 ) * 16
      nNum += AT( SubStr( cData, nX+1, 1 ), "0123456789ABCDEF" ) - 1
      HB_BPOKE( @cRet, (nX+1)/2, nNum )
   NEXT

RETU cRet

METHOD GetSignature( cHeader, cPayload, cSecret, cAlgorithm ) CLASS JWT
  LOCAL cSignature := ""

  DO CASE
     CASE cAlgorithm=="HS256"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA256( cHeader + '.' + cPayload, cSecret ) ) )
     CASE cAlgorithm=="HS384"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA384( cHeader + '.' + cPayload, cSecret ) ) )
     CASE cAlgorithm=="HS512"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA512( cHeader + '.' + cPayload, cSecret ) ) )
     OTHERWISE
         ::cError := "INVALID ALGORITHM"
  ENDCASE
RETU cSignature

METHOD Decode( cJWT ) CLASS JWT

  LOCAL aJWT

  // Reset Object
  ::Reset()

  // Check JWT
  IF VALTYPE(cJWT)!="C"
      ::cError := "Invalid JWT: not character ["+VALTYPE(cJWT)+"]"
      RETU .F.
  ENDIF

  //  Split JWT
  aJWT := HB_ATokens( cJWT, '.' )
  IF LEN(aJWT) <> 3
      ::cError := "Invalid JWT"
      RETU .F.
  ENDIF

  // Explode header
  ::aHeader   := hb_jsonDecode( ::Base64UrlDecode( aJWT[1] ))

  // Exploce payload
  ::aPayload  := hb_jsonDecode( ::Base64UrlDecode( aJWT[2] ))

RETU .T.

METHOD Verify( cJWT ) CLASS JWT

  LOCAL aJWT, aHeader, aPayload
  LOCAL cSignature, cNewSignature

  // Check JWT
  IF VALTYPE(cJWT)!="C"
      ::cError := "Invalid JWT: not character ["+VALTYPE(cJWT)+"]"
      RETU .F.
  ENDIF

  //  Split JWT
  aJWT := HB_ATokens( cJWT, '.' )
  IF LEN(aJWT) <> 3
      ::cError := "Invalid JWT"
      RETU .F.
  ENDIF

  // Explode header
  aHeader   := hb_jsonDecode( ::Base64UrlDecode( aJWT[1] ))

  // Check aHeader
  IF VALTYPE(aHeader)!="H"
      ::cError := "Invalid JWT: header not base64"
      RETU .F.
  ENDIF

  // Exploce payload
  aPayload   := hb_jsonDecode( ::Base64UrlDecode( aJWT[2] ))

  // Check aPayload
  IF VALTYPE(aPayload)!="H"
      ::cError := "Invalid JWT: payload not base64"
      RETU .F.
  ENDIF

  // Get signature
  cSignature  := aJWT[3]

  // Calculate new sicnature
  cNewSignature   := ::GetSignature( aJWT[1], aJWT[2], ::cSecret, aHeader[ 'alg' ] )
  IF ( cSignature != cNewSignature )
    ::cError := "Invalid signature"
    RETU .F.
  ENDIF

  // Check Issuer
  IF !::CheckPayload(aPayload, 'iss')
     ::cError := "Different issuer"
     RETU .F.
  ENDIF

  // Check Subject
  IF !::CheckPayload(aPayload, 'sub')
     ::cError := "Different subject"
     RETU .F.
  ENDIF

  // Check Audience
  IF !::CheckPayload(aPayload, 'aud')
     ::cError := "Different audience"
     RETU .F.
  ENDIF

  // Check expiration
  IF hb_HHasKey(aPayLoad,'exp')
     IF aPayLoad[ 'exp' ] < ::GetSeconds()
       ::cError := "Token expired"
       RETU .F.
     ENDIF
  ENDIF

  // Check not before
  IF hb_HHasKey(aPayLoad,'nbf')
     IF aPayLoad[ 'nbf' ] > ::GetSeconds()
       ::cError := "Token not valid until:" +STR(aPayLoad[ 'nbf' ])
       RETU .F.
     ENDIF
  ENDIF

  // Check issuedAt
  IF hb_HHasKey(aPayLoad,'iat')
     IF aPayLoad[ 'iat' ] > ::GetSeconds()
       ::cError := "Token issued in future:" +STR(aPayLoad[ 'iat' ])
       RETU .F.
     ENDIF
  ENDIF

  // Check JWT id
  IF !::CheckPayload(aPayload, 'jti')
     ::cError := "Different JWT id"
     RETU .F.
  ENDIF

  // Check Type
  IF !::CheckPayload(aPayload, 'typ')
     ::cError := "Different JWT type"
     RETU .F.
  ENDIF

RETU .T.

METHOD GetSeconds() CLASS JWT

  LOCAL posixday := date() - STOD("19700101")
  LOCAL cTime := time()
  LOCAL posixsec := posixday * 24 * 60 * 60

RETU posixsec + (int(val(substr(cTime,1,2))) * 3600) + (int(val(substr(cTime,4.2))) * 60) + ( int(val(substr(cTime,7,2))) )

METHOD CheckPayload(aPayload, cKey)
  IF hb_HHasKey(aPayLoad,cKey) .AND. hb_HHasKey(::aPayLoad,cKey)
     IF aPayLoad[ cKey ] != ::aPayLoad[ cKey ]
       RETU .F.
     ENDIF
  ELSEIF hb_HHasKey(aPayLoad,cKey) .OR. hb_HHasKey(::aPayLoad,cKey)
     RETU .F.
  ENDIF
RETU .T.
Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
Re: return json hix / mod_harbour
Posted: Thu Feb 05, 2026 12:16 AM

Otto... Buena noche

Mi app de escritorio fivewin+xharbour, la usan unos 300 o mas clientes...( la mayorĂ­a pos) mi app web ( angular + jakarta ee + node)... la usan 20 clientes( grandes no pos )

Ya conozco angular y tengo desarrollo en angular.. y me cuesta mas trabajo hacerlo sin un framework... Para mi el desarrollo de la vista no es problema.. solo tengo que adaptarla en forma a una vista que sea más o menos igual a lo que tengo en fivewin... de tal forma que no se impacten a estos clientes pequeños...

Realmente lo complejo seria ... como aprovechar el cĂłdigo de la lĂłgica del negocio o que al reescribir el backend en hix o mod_harbour,,,se pueda aprovechar gran parte de este cĂłdigo .. Al final es dejar el desarrollo de app locales o de PC...

Por eso es que no pido asesorĂ­a en html o js... solo requiero es dominar mod_harbour como servidor backend... ahora si me complica o no puedo aprovechar los fuentes actuales (nostalgia por los prg,dbf,sql).... me irĂ­a por lo que ya conozco, como es nodejs (sql)...

Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Thu Feb 05, 2026 12:36 AM

Jonsson,
Thanks. When I read this, one key question immediately comes to mind:
How have you organized your data access up to now?

Do you open the database at application startup and keep it open during runtime, or do you open and close the tables on each individual interaction?

I think this is crucial, because in this setup there is no direct connection anymore like in a classic desktop application. Everything works via request and response: the frontend asks something like “please send me record number 1” and the backend replies. All communication goes back and forth using JSON.

If your application has always worked with databases opened at startup and kept open, then this is a significant change — not only in flow, but also in overall logic and architecture.

Choosing a system is never easy, but in your case part of the decision has already been made, since you are now mainly looking for a backend solution.

In my own case, I solved it like this: in addition to the web server, I run a separate print server on the server. All receipts — in the POS case, the full tickets — are generated as files. The print server watches for these files, picks them up, and prints them.

With POS systems, the real challenge is usually printer control, depending on how many printers are connected. I develop POS systems for hospitality, and there are often multiple printers per terminal: bar, kitchen, beverage station, salad station, and so on.

To give truly solid and well-founded advice, one would need to see at least part of the source code — especially the sections that handle database open/close logic and how the business logic is structured.
Best regards,
Otto

Posts: 1283
Joined: Fri Feb 10, 2006 02:34 PM
Re: return json hix / mod_harbour
Posted: Thu Feb 05, 2026 06:19 AM

Jonsson,

russimicro wrote:
  1. Generar el token JWT - harbour

Generacion JWT

Code (php): Select all Collapse
function main()

   local aH := {=>}
   local o := UJWT():New( 'MyKey' )

   aH[ 'time' ] := time()
   aH[ 'date' ] := date()
   aH[ 'age'  ] := 123
   aH[ 'married' ] := .T.

   o:SetData( aH )

retu o:encode()

Recuperacion

Code (php): Select all Collapse
function main()

   local hParam := UGet()
   local o  := UJWT():New( 'MyKey' )
   local hData := o:decode( hParam[ '_token' ] )

   UAddHeader( "Content-Type", "application/json" )
   UAddHeader( "Access-Control-Allow-Origin", '*')
	
retu hb_jsonEncode( hData )

C.

Salutacions, saludos, regards

"...programar es fácil, hacer programas es difícil..."

UT Page -> https://carles9000.github.io/
Forum UT -> https://discord.gg/bq8a9yGMWh
HIX -> https://github.com/carles9000/hix
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Thu Feb 05, 2026 07:23 AM

Jonsson,
I’ve thought about your question again today. Given that you have such a large user base, you carry a significant responsibility. For that reason, I believe the choice of which system to use should be made from a responsibility-driven perspective, not just a technical one.

From my point of view, this means one of two paths:
Either you rely on a standard stack such as Apache, PHP, and a well-established backend, or—if you choose a Harbour-based solution—you should make sure that you truly have access to the complete source code, including the server-side C code.

This became very clear to me while building my own microservice. When looking at the actual Harbour source code on GitHub, I noticed that several components which are mentioned in Harbour documentation—especially around WebSocket support—are no longer available or downloadable in the core source itself.

Because of that, I decided to implement the WebSocket communication myself in C using #pragma within the microservice. Not because I wanted additional complexity, but because I wanted full control, clear ownership, and independence from parts that might change, disappear, or no longer be maintained.

Any project of this kind has to be designed with long-term sustainability in mind. That also means being prepared to make changes yourself when necessary. This isn’t a drawback—it’s part of the responsibility that comes with running a system in production over many years.
Best regards,
Otto

Posts: 410
Joined: Sun Jan 31, 2010 03:30 PM
Re: return json hix / mod_harbour
Posted: Thu Feb 05, 2026 12:39 PM

Buen dia...

Carles.. funciono la case UJWT... Gracias

Otto... en torno a apertura de las tablas.. estoy implementando la misma técnica usada en la web... trabajar desconectado,
=> abrir - consultar - cerrar - gestionar en objeto TarrayData la respuesta ()--- al final un JSON...

En torno al soporte técnico... si es muy importante... y creo que es el más importante... ya pasé por esa situación con SQLRDD de xhabour.com....

LEANDRO expuso este tema en este foro, lo que conlleva a tener un plan de contingencia...

Para usuarios de mod_harbour que usan la clase JWT de Matteo Baccan, lean este link :

https://pentesterlab.com/blog/cve-2026-23993-harbourjwt-unknown-alg-jwt-bypass

Uso de JWT :

PROCEDURE prueba_jwt()


LOCAL oJwt := JWT():New()
LOCAL hPayload := { "user" => "johnson", "role" => "admin" }
LOCAL cToken, hDecoded

// cAlgorithm=="HS256" .OR. cAlgorithm=="HS384" .OR. cAlgorithm=="HS512"
oJwt:SetAlgorithm( "HS512" )

// Generar token
cToken := oJwt:Encode( hPayload, "miClaveSecreta" )
? "Token:", cToken

// Validar token
hDecoded := oJwt:Decode( cToken, "miClaveSecreta" )
? "Payload:", hb_jsonEncode( hDecoded )
RETURN

/// CLASE CORREGIDA SEGUN LLM del link anterior

CLASS JWT

HIDDEN:

  CLASSDATA cSecret
  CLASSDATA aHeader
  CLASSDATA aPayload
  CLASSDATA cError

  METHOD Base64UrlEncode( cData )
  METHOD Base64UrlDecode( cData )
  METHOD ByteToString( cData )
  METHOD GetSignature( cHeader, cPayload, cSecret, cAlgorithm )
  METHOD CheckPayload(aPayload, cKey)

EXPORTED:

  METHOD New() CONSTRUCTOR

  // Header
  METHOD SetType( cType )
  METHOD GetType()                          INLINE ::aHeader[ 'typ' ]
  METHOD SetContentType( cContentType )     INLINE ::aHeader[ 'cty' ] :=  cContentType
  METHOD GetContentType()                   INLINE ::aHeader[ 'cty' ]
  METHOD SetAlgorithm( cAlgorithm )
  METHOD GetAlgorithm()                     INLINE ::aHeader[ 'alg' ]

  // Payload
  METHOD SetIssuer( cIssuer )               INLINE ::SetPayloadData('iss', cIssuer)
  METHOD GetIssuer()                        INLINE ::GetPayloadData('iss')
  METHOD SetSubject( cSubject )             INLINE ::SetPayloadData('sub', cSubject)
  METHOD GetSubject()                       INLINE ::GetPayloadData('sub')
  METHOD SetAudience( cAudience )           INLINE ::SetPayloadData('aud', cAudience)
  METHOD GetAudience()                      INLINE ::GetPayloadData('aud')
  METHOD SetExpration( nExpiration )        INLINE ::SetPayloadData('exp', nExpiration)
  METHOD GetExpration()                     INLINE ::GetPayloadData('exp')
  METHOD SetNotBefore( nNotBefore )         INLINE ::SetPayloadData('nbf', nNotBefore)
  METHOD GetNotBefore()                     INLINE ::GetPayloadData('nbf')
  METHOD SetIssuedAt( nIssuedAt )           INLINE ::SetPayloadData('iat', nIssuedAt)
  METHOD GetIssuedAt()                      INLINE ::GetPayloadData('iat')
  METHOD SetJWTId( cJWTId )                 INLINE ::SetPayloadData('jti', cJWTId)
  METHOD GetJWTId()                         INLINE ::GetPayloadData('jti')

  // Payload methods
  METHOD SetPayloadData( cKey, uValue )     INLINE IF( uValue==NIL, hb_HDel(::aPayload,cKey), ::aPayload[cKey] := uValue)
  METHOD GetPayloadData( cKey )             INLINE IF( hb_HHasKey(::aPayLoad,cKey), ::aPayload[cKey], NIL )

  // Secret
  METHOD SetSecret( cSecret )               INLINE ::cSecret := cSecret
  METHOD GetSecret()                        INLINE ::cSecret

  // Error
  METHOD GetError()                         INLINE ::cError

  // Cleanup: aHeader, aPayload, cError, cSecret
  METHOD Reset()

  // Encode a JWT and return it
  METHOD Encode()

  // Decode a JWT
  METHOD Decode( cJWT )

  // Decode a JWT
  METHOD Verify( cJWT )

  // Getter internal data with internal exposion
  METHOD GetPayload()                       INLINE hb_hClone(::aPayload)
  METHOD GetHeader()                        INLINE hb_hClone(::aHeader)

  // Helper method for expiration setting
  METHOD GetSeconds()

  // Versione
  METHOD GetVersion()                       INLINE "1.0.2"

ENDCLASS

METHOD New() CLASS JWT
  ::Reset()
RETU SELF

// Optional
METHOD SetType( cType ) CLASS JWT
  LOCAL bRet := .F.

  IF cType=="JWT"
      ::aHeader[ 'typ' ] := cType
  ELSE
      bRet := .F.
      ::cError := "Invalid type [" +cType +"]"
  ENDIF

RETU bRet

// Mandatory
METHOD SetAlgorithm( cAlgorithm ) CLASS JWT
  LOCAL bRet := .F.

  IF cAlgorithm=="HS256" .OR. cAlgorithm=="HS384" .OR. cAlgorithm=="HS512"
      ::aHeader[ 'alg' ] := cAlgorithm
  ELSE
      bRet := .F.
      ::cError := "Invalid algorithm [" +cAlgorithm +"]"
  ENDIF

RETU bRet

METHOD Reset() CLASS JWT

  ::aHeader   := {=>}
  ::aPayload := {=>}
  ::cError  := ''
  ::cSecret  := ''

RETU NIL


METHOD Encode() CLASS JWT

  LOCAL cHeader
  LOCAL cPayload
  LOCAL cSignature

  //  Encode header
  cHeader     := ::Base64UrlEncode( hb_jsonEncode( ::aHeader ) )

  // Encode payload
  cPayload    := ::Base64UrlEncode( hb_jsonEncode( ::aPayload ) )

  //  Make signature
  cSignature := ::GetSignature( cHeader, cPayload, ::cSecret, ::aHeader[ 'alg' ] )

//  Return JWT
RETU cHeader + '.' + cPayload + '.' + cSignature

METHOD Base64UrlEncode( cData ) CLASS JWT
RETU hb_StrReplace( hb_base64Encode( cData ), "+/=", { "-", "_", "" } )

METHOD Base64UrlDecode( cData ) CLASS JWT
RETU hb_base64Decode( hb_StrReplace( cData, "-_", "+/" ) )

METHOD ByteToString( cData ) CLASS JWT
   LOCAL cRet := SPACE(LEN(cData)/2)
   LOCAL nLen := LEN( cData )
   LOCAL nX, nNum

   cData := UPPER(cData)
   FOR nX := 1 TO nLen STEP 2
      nNum := ( AT( SubStr( cData, nX  , 1 ), "0123456789ABCDEF" ) - 1 ) * 16
      nNum += AT( SubStr( cData, nX+1, 1 ), "0123456789ABCDEF" ) - 1
      HB_BPOKE( @cRet, (nX+1)/2, nNum )
   NEXT

RETU cRet

METHOD GetSignature( cHeader, cPayload, cSecret, cAlgorithm ) CLASS JWT
  LOCAL cSignature := ""

  DO CASE
     CASE cAlgorithm=="HS256"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA256( cHeader + '.' + cPayload, cSecret ) ) )
     CASE cAlgorithm=="HS384"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA384( cHeader + '.' + cPayload, cSecret ) ) )
     CASE cAlgorithm=="HS512"
         cSignature := ::Base64UrlEncode( ::ByteToString( HB_HMAC_SHA512( cHeader + '.' + cPayload, cSecret ) ) )
     OTHERWISE
         ::cError := "INVALID ALGORITHM"
  ENDCASE
RETU cSignature

METHOD Decode( cJWT ) CLASS JWT

  LOCAL aJWT

  // Reset Object
  ::Reset()

  // Check JWT
  IF VALTYPE(cJWT)!="C"
      ::cError := "Invalid JWT: not character ["+VALTYPE(cJWT)+"]"
      RETU .F.
  ENDIF

  //  Split JWT
  aJWT := HB_ATokens( cJWT, '.' )
  IF LEN(aJWT) <> 3
      ::cError := "Invalid JWT"
      RETU .F.
  ENDIF

  // Explode header
  ::aHeader   := hb_jsonDecode( ::Base64UrlDecode( aJWT[1] ))

  // Exploce payload
  ::aPayload  := hb_jsonDecode( ::Base64UrlDecode( aJWT[2] ))

RETU .T.

METHOD Verify( cJWT ) CLASS JWT

  LOCAL aJWT, aHeader, aPayload
  LOCAL cSignature, cNewSignature

  ::cError := ''

  // Check JWT
  IF VALTYPE(cJWT)!="C"
      ::cError := "Invalid JWT: not character ["+VALTYPE(cJWT)+"]"
      RETU .F.
  ENDIF

  //  Split JWT
  aJWT := HB_ATokens( cJWT, '.' )
  IF LEN(aJWT) <> 3
      ::cError := "Invalid JWT"
      RETU .F.
  ENDIF

  // Explode header
  aHeader   := hb_jsonDecode( ::Base64UrlDecode( aJWT[1] ))

  // Check aHeader
  IF VALTYPE(aHeader)!="H"
      ::cError := "Invalid JWT: header not base64"
      RETU .F.
  ENDIF

  // Exploce payload
  aPayload   := hb_jsonDecode( ::Base64UrlDecode( aJWT[2] ))

  // Check aPayload
  IF VALTYPE(aPayload)!="H"
      ::cError := "Invalid JWT: payload not base64"
      RETU .F.
  ENDIF

  // Get signature
  cSignature  := aJWT[3]

  // Calculate new sicnature
  cNewSignature   := ::GetSignature( aJWT[1], aJWT[2], ::cSecret, aHeader[ 'alg' ] )
  IF ( cSignature != cNewSignature )  .OR. !EMPTY(::cError)
    ::cError := "Invalid signature"
    RETU .F.
  ENDIF

  // Check Issuer
  IF !::CheckPayload(aPayload, 'iss')
     ::cError := "Different issuer"
     RETU .F.
  ENDIF

  // Check Subject
  IF !::CheckPayload(aPayload, 'sub')
     ::cError := "Different subject"
     RETU .F.
  ENDIF

  // Check Audience
  IF !::CheckPayload(aPayload, 'aud')
     ::cError := "Different audience"
     RETU .F.
  ENDIF

  // Check expiration
  IF hb_HHasKey(aPayLoad,'exp')
     IF aPayLoad[ 'exp' ] < ::GetSeconds()
       ::cError := "Token expired"
       RETU .F.
     ENDIF
  ENDIF

  // Check not before
  IF hb_HHasKey(aPayLoad,'nbf')
     IF aPayLoad[ 'nbf' ] > ::GetSeconds()
       ::cError := "Token not valid until:" +STR(aPayLoad[ 'nbf' ])
       RETU .F.
     ENDIF
  ENDIF

  // Check issuedAt
  IF hb_HHasKey(aPayLoad,'iat')
     IF aPayLoad[ 'iat' ] > ::GetSeconds()
       ::cError := "Token issued in future:" +STR(aPayLoad[ 'iat' ])
       RETU .F.
     ENDIF
  ENDIF

  // Check JWT id
  IF !::CheckPayload(aPayload, 'jti')
     ::cError := "Different JWT id"
     RETU .F.
  ENDIF

  // Check Type
  IF !::CheckPayload(aPayload, 'typ')
     ::cError := "Different JWT type"
     RETU .F.
  ENDIF

RETU .T.

METHOD GetSeconds() CLASS JWT

  LOCAL posixday := date() - STOD("19700101")
  LOCAL cTime := time()
  LOCAL posixsec := posixday * 24 * 60 * 60

RETU posixsec + (int(val(substr(cTime,1,2))) * 3600) + (int(val(substr(cTime,4.2))) * 60) + ( int(val(substr(cTime,7,2))) )

METHOD CheckPayload(aPayload, cKey)
  IF hb_HHasKey(aPayLoad,cKey) .AND. hb_HHasKey(::aPayLoad,cKey)
     IF aPayLoad[ cKey ] != ::aPayLoad[ cKey ]
       RETU .F.
     ENDIF
  ELSEIF hb_HHasKey(aPayLoad,cKey) .OR. hb_HHasKey(::aPayLoad,cKey)
     RETU .F.
  ENDIF
RETU .T.


//***************************************************************************************
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: return json hix / mod_harbour
Posted: Thu Feb 05, 2026 01:20 PM

Hi Jonsson,
I converted the JWT to pure funcrions - can you test if you get the same results?

#include "fivewin.ch"
#include "hbjson.ch"





/* =========================================================
   TEST FUNCTION
   ========================================================= */

PROCEDURE main()

   LOCAL hHeader := { ;
      "typ"=>"JWT", ;
      "alg"=>"HS256" ;
   }

   LOCAL hPayload := { ;
      "iss"=>"harbour-test", ;
      "sub"=>"user123", ;
      "iat"=>jwt_now(), ;
      "exp"=>jwt_now()+60 ;
   }

   LOCAL cSecret := "super-secret-key"
   LOCAL cJWT
   LOCAL hResult

   ? "=== JWT TEST ==="
   ? ""

   cJWT := jwt_encode( hHeader, hPayload, cSecret )

   ? "Generated JWT:"
   ? cJWT
   ? ""

   hResult := jwt_verify( cJWT, cSecret, { "iss"=>"harbour-test" } )

   ? "Verify result:"
   ? hb_jsonEncode( hResult, .T. )

RETURN









/* =========================================================
   Base64 URL helpers
   ========================================================= */

FUNCTION jwt_base64url_encode( cData )
RETURN hb_StrReplace( hb_base64Encode( cData ), "+/=", { "-", "_", "" } )

FUNCTION jwt_base64url_decode( cData )
RETURN hb_base64Decode( hb_StrReplace( cData, "-_", "+/" ) )

FUNCTION jwt_hex_to_bytes( cHex )
   LOCAL cRet := SPACE( LEN(cHex) / 2 )
   LOCAL i, n

   cHex := Upper( cHex )
   FOR i := 1 TO LEN(cHex) STEP 2
      n := ( AT( SubStr(cHex,i,1), "0123456789ABCDEF" ) - 1 ) * 16
      n += AT( SubStr(cHex,i+1,1), "0123456789ABCDEF" ) - 1
      HB_BPOKE( @cRet, (i+1)/2, n )
   NEXT
RETURN cRet

/* =========================================================
   HMAC signature
   ========================================================= */

FUNCTION jwt_signature( cHeader, cPayload, cSecret, cAlg )
   LOCAL cData := cHeader + "." + cPayload

   DO CASE
      CASE cAlg == "HS256"
         RETURN jwt_base64url_encode( ;
            jwt_hex_to_bytes( HB_HMAC_SHA256( cData, cSecret ) ) )

  CASE cAlg == "HS384"
     RETURN jwt_base64url_encode( ;
        jwt_hex_to_bytes( HB_HMAC_SHA384( cData, cSecret ) ) )

  CASE cAlg == "HS512"
     RETURN jwt_base64url_encode( ;
        jwt_hex_to_bytes( HB_HMAC_SHA512( cData, cSecret ) ) )
   ENDCASE

RETURN NIL

/* =========================================================
   Time helper (POSIX seconds)
   ========================================================= */

FUNCTION jwt_now()
   LOCAL d := Date() - STOD("19700101")
   LOCAL t := Time()
RETURN d*86400 + ;
       Val(SubStr(t,1,2))*3600 + ;
       Val(SubStr(t,4,2))*60 + ;
       Val(SubStr(t,7,2))

/* =========================================================
   Encode JWT
   ========================================================= */

FUNCTION jwt_encode( hHeader, hPayload, cSecret )
   LOCAL cHeader, cPayload, cSig

   cHeader  := jwt_base64url_encode( hb_jsonEncode( hHeader ) )
   cPayload := jwt_base64url_encode( hb_jsonEncode( hPayload ) )

   cSig := jwt_signature( cHeader, cPayload, cSecret, hHeader["alg"] )

RETURN cHeader + "." + cPayload + "." + cSig

/* =========================================================
   Verify JWT
   ========================================================= */

FUNCTION jwt_verify( cJWT, cSecret, hExpected )
   LOCAL aParts, hHeader, hPayload, cSig, cNewSig

   IF ValType(cJWT) != "C"
      RETURN { "ok"=>.F., "error"=>"Invalid token type" }
   ENDIF

   aParts := HB_ATokens( cJWT, "." )
   IF Len(aParts) != 3
      RETURN { "ok"=>.F., "error"=>"Malformed token" }
   ENDIF

   hHeader  := hb_jsonDecode( jwt_base64url_decode( aParts[1] ) )
   hPayload := hb_jsonDecode( jwt_base64url_decode( aParts[2] ) )

   cSig    := aParts[3]
   cNewSig := jwt_signature( aParts[1], aParts[2], cSecret, hHeader["alg"] )

   IF cSig != cNewSig
      RETURN { "ok"=>.F., "error"=>"Invalid signature" }
   ENDIF

   IF hb_HHasKey( hPayload, "exp" )
      IF hPayload["exp"] < jwt_now()
         RETURN { "ok"=>.F., "error"=>"Token expired" }
      ENDIF
   ENDIF

   IF hExpected != NIL .AND. hb_HHasKey( hExpected, "iss" )
      IF hPayload["iss"] != hExpected["iss"]
         RETURN { "ok"=>.F., "error"=>"Issuer mismatch" }
      ENDIF
   ENDIF

RETURN { "ok"=>.T., "payload"=>hPayload }