mirror of
https://github.com/acanas/swad-core.git
synced 2024-09-22 00:00:40 +02:00
2924 lines
96 KiB
C
2924 lines
96 KiB
C
// swad_string.c: string processing
|
|
|
|
/*
|
|
SWAD (Shared Workspace At a Distance),
|
|
is a web platform developed at the University of Granada (Spain),
|
|
and used to support university teaching.
|
|
|
|
This file is part of SWAD core.
|
|
Copyright (C) 1999-2018 Antonio Cañas Vargas
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*****************************************************************************/
|
|
/********************************* Headers ***********************************/
|
|
/*****************************************************************************/
|
|
|
|
#include <linux/stddef.h> // For NULL
|
|
#include <ctype.h> // For isprint, isspace, etc.
|
|
#include <locale.h> // For setlocale
|
|
#include <math.h> // For log10, floor, ceil, modf, sqrt...
|
|
#include <stdlib.h> // For malloc and free
|
|
#include <string.h> // For string functions
|
|
|
|
#include "swad_global.h"
|
|
#include "swad_ID.h"
|
|
#include "swad_parameter.h"
|
|
#include "swad_string.h"
|
|
|
|
/*****************************************************************************/
|
|
/******************** Global variables from other modules ********************/
|
|
/*****************************************************************************/
|
|
|
|
extern struct Globals Gbl; // Declaration in swad.c
|
|
|
|
/*****************************************************************************/
|
|
/****************************** Internal types *******************************/
|
|
/*****************************************************************************/
|
|
|
|
/*****************************************************************************/
|
|
/*************************** Internal prototypes *****************************/
|
|
/*****************************************************************************/
|
|
|
|
static unsigned Str_GetNextASCIICharFromStr (const char *Ptr,unsigned char *Ch);
|
|
|
|
static unsigned Str_FindHTMLEntity (const char *Ptr);
|
|
|
|
static int Str_ReadCharAndSkipComments (FILE *FileSrc,Str_SkipHTMLComments_t SkipHTMLComments);
|
|
static int Str_ReadCharAndSkipCommentsWriting (FILE *FileSrc,FILE *FileTgt,Str_SkipHTMLComments_t SkipHTMLComments);
|
|
static int Str_ReadCharAndSkipCommentsBackward (FILE *FileSrc,Str_SkipHTMLComments_t SkipHTMLComments);
|
|
|
|
/*****************************************************************************/
|
|
/**************************** Internal constants *****************************/
|
|
/*****************************************************************************/
|
|
|
|
/***** Conversion to Base64URL *****/
|
|
// base64url is described in document http://tools.ietf.org/html/rfc4648.
|
|
// It uses '-' and '_' because they are safe for URL/parameters (without enconding) and for filenames.
|
|
const char Str_BIN_TO_BASE64URL[64] =
|
|
{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
|
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
|
|
'0','1','2','3','4','5','6','7','8','9','-','_'};
|
|
|
|
static const char Str_LF[2] = {10,0};
|
|
static const char Str_CR[2] = {13,0};
|
|
|
|
/*****************************************************************************/
|
|
/****************************** Internal types *******************************/
|
|
/*****************************************************************************/
|
|
|
|
/*****************************************************************************/
|
|
/****************** Insert a link in every URL or nickname *******************/
|
|
/*****************************************************************************/
|
|
/*
|
|
Insertion example:
|
|
The web site of @rms is https://stallman.org/
|
|
The web site of <a href="https://openswad.org/?usr=@rms">@rms</a> is <a href="https://stallman.org/" target="_blank">https://stallman.org/</a>
|
|
*/
|
|
|
|
/*
|
|
|
|
<form id="form_z7FY4oFr-yot9R9xRVwDhIntnod3geLj36nyQ6jlJJs_53"
|
|
action="https://localhost/swad/es" method="post">
|
|
<input type="hidden" name="ses" value="2jb9CGhIJ81_qhDyeQ6MWDFKQ5ZaA_F68tq22ZAjYww">
|
|
<input type="hidden" name="usr" value="@acanas">
|
|
<a href="" onclick="document.getElementById('form_z7FY4oFr-yot9R9xRVwDhIntnod3geLj36nyQ6jlJJs_53').submit();return false;">
|
|
@acanas
|
|
</a>
|
|
</form>
|
|
|
|
*/
|
|
|
|
#define ANCHOR_1_URL "<a href=\""
|
|
#define ANCHOR_2_URL "\" target=\"_blank\">"
|
|
#define ANCHOR_3_URL "</a>"
|
|
#define ANCHOR_3_NICK "</a></form>"
|
|
|
|
#define MAX_LINKS 1000
|
|
|
|
#define MAX_BYTES_LIMITED_URL (1024 - 1) // Max. number of bytes of the URL shown on screen
|
|
|
|
void Str_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScreen)
|
|
{
|
|
extern const char *Txt_STR_LANG_ID[1 + Txt_NUM_LANGUAGES];
|
|
char ParamsStr[256 + 256 + Ses_BYTES_SESSION_ID + 256];
|
|
char Anchor1Nick[256 + 256 + 256 + Ses_BYTES_SESSION_ID + 256 + 256];
|
|
char Anchor2Nick[256 + Cry_BYTES_ENCRYPTED_STR_SHA256_BASE64];
|
|
size_t TxtLength;
|
|
size_t TxtLengthWithInsertedAnchors;
|
|
|
|
size_t Anchor1URLLength;
|
|
size_t Anchor2URLLength;
|
|
size_t Anchor3URLLength;
|
|
size_t AnchorURLTotalLength;
|
|
|
|
size_t Anchor1NickLength = 0; // Initialized only to avoid warning
|
|
size_t Anchor2NickLength = 0; // Initialized only to avoid warning
|
|
size_t Anchor3NickLength;
|
|
size_t AnchorNickTotalLength = 0; // Initialized only to avoid warning
|
|
|
|
char *PtrSrc;
|
|
char *PtrDst;
|
|
bool URLStartFound;
|
|
bool IsNickname;
|
|
int NumLinks = 0;
|
|
int NumLink;
|
|
struct
|
|
{
|
|
char *PtrStart; // Pointer to the first char of URL/nickname in original text
|
|
char *PtrEnd; // Pointer to the last char of URL/nickname in original text
|
|
size_t NumActualBytes; // Actual length of the URL/nickname
|
|
char *Anchor1Nick;
|
|
char *Anchor2Nick;
|
|
size_t Anchor1NickLength;
|
|
size_t Anchor2NickLength;
|
|
size_t AddedLengthUntilHere; // Total length of extra HTML code added until this link (included)
|
|
} Links[MAX_LINKS];
|
|
size_t LengthVisibleLink;
|
|
size_t Length;
|
|
size_t i;
|
|
size_t NumChars1;
|
|
size_t NumChars2;
|
|
size_t NumBytesToCopy;
|
|
size_t NumBytesToShow; // Length of the link displayed on screen (may be shorter than actual length)
|
|
char LimitedURL[MAX_BYTES_LIMITED_URL + 1];
|
|
unsigned char Ch;
|
|
|
|
/****** Initialize constant anchors and their lengths *****/
|
|
TxtLength = strlen (Txt);
|
|
|
|
// For URLs the length of anchor is fixed
|
|
// so it can be calculated once
|
|
Anchor1URLLength = strlen (ANCHOR_1_URL);
|
|
Anchor2URLLength = strlen (ANCHOR_2_URL);
|
|
Anchor3URLLength = strlen (ANCHOR_3_URL);
|
|
AnchorURLTotalLength = Anchor1URLLength +
|
|
Anchor2URLLength +
|
|
Anchor3URLLength;
|
|
|
|
// For nicknames the length of anchor is variable
|
|
// so it can be calculated for each link,
|
|
// except the third part that is fixed
|
|
Anchor3NickLength = strlen (ANCHOR_3_NICK);
|
|
|
|
/**************************************************************/
|
|
/***** Find starts and ends of links (URLs and nicknames) *****/
|
|
/**************************************************************/
|
|
for (PtrSrc = Txt;
|
|
*PtrSrc;)
|
|
/* Check if the next char is the start of a URL */
|
|
if (tolower ((int) *PtrSrc) == (int) 'h')
|
|
{
|
|
URLStartFound = false;
|
|
Links[NumLinks].PtrStart = PtrSrc;
|
|
if (tolower ((int) *++PtrSrc) == (int) 't') // ht...
|
|
{
|
|
if (tolower ((int) *++PtrSrc) == (int) 't') // htt...
|
|
{
|
|
if (tolower ((int) *++PtrSrc) == (int) 'p') // http...
|
|
{
|
|
PtrSrc++;
|
|
if (*PtrSrc == ':') // http:...
|
|
{
|
|
if (*++PtrSrc == '/') // http:/...
|
|
if (*++PtrSrc == '/') // http://...
|
|
URLStartFound = true;
|
|
}
|
|
else if (tolower ((int) *PtrSrc) == (int) 's') // https...
|
|
{
|
|
if (*++PtrSrc == ':') // https:...
|
|
{
|
|
if (*++PtrSrc == '/') // https:/...
|
|
if (*++PtrSrc == '/') // https://...
|
|
URLStartFound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (URLStartFound)
|
|
{
|
|
/* Find URL end */
|
|
PtrSrc++; // Points to first character after http:// or https://
|
|
for (;;)
|
|
{
|
|
NumChars1 = Str_GetNextASCIICharFromStr (PtrSrc,&Ch);
|
|
PtrSrc += NumChars1;
|
|
if (Ch <= 32 || Ch == '<' || Ch == '"')
|
|
{
|
|
Links[NumLinks].PtrEnd = PtrSrc - NumChars1 - 1;
|
|
break;
|
|
}
|
|
else if (Ch == ',' || Ch == '.' || Ch == ';' || Ch == ':' || Ch == ')' || Ch == ']' || Ch == '}')
|
|
{
|
|
NumChars2 = Str_GetNextASCIICharFromStr (PtrSrc,&Ch);
|
|
PtrSrc += NumChars2;
|
|
if (Ch <= 32 || Ch == '<' || Ch == '"')
|
|
{
|
|
Links[NumLinks].PtrEnd = PtrSrc - NumChars2 - NumChars1 - 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Initialize anchors for this link */
|
|
Links[NumLinks].Anchor1Nick = NULL;
|
|
Links[NumLinks].Anchor2Nick = NULL;
|
|
|
|
/* Calculate length of this URL */
|
|
Links[NumLinks].NumActualBytes = (size_t) (Links[NumLinks].PtrEnd + 1 - Links[NumLinks].PtrStart);
|
|
if (Links[NumLinks].NumActualBytes <= MaxCharsURLOnScreen)
|
|
LengthVisibleLink = Links[NumLinks].NumActualBytes;
|
|
else // If URL is too long to be displayed ==> short it
|
|
{
|
|
/* Make a copy of this URL */
|
|
NumBytesToCopy = (Links[NumLinks].NumActualBytes < MAX_BYTES_LIMITED_URL) ? Links[NumLinks].NumActualBytes :
|
|
MAX_BYTES_LIMITED_URL;
|
|
strncpy (LimitedURL,Links[NumLinks].PtrStart,NumBytesToCopy);
|
|
LimitedURL[NumBytesToCopy] = '\0';
|
|
|
|
/* Limit the number of characters on screen of the copy, and calculate its length in bytes */
|
|
LengthVisibleLink = Str_LimitLengthHTMLStr (LimitedURL,MaxCharsURLOnScreen);
|
|
}
|
|
if (NumLinks == 0)
|
|
Links[NumLinks].AddedLengthUntilHere = AnchorURLTotalLength + LengthVisibleLink;
|
|
else
|
|
Links[NumLinks].AddedLengthUntilHere = Links[NumLinks - 1].AddedLengthUntilHere +
|
|
AnchorURLTotalLength + LengthVisibleLink;
|
|
|
|
/* Increment number of found links */
|
|
NumLinks++;
|
|
if (NumLinks == MAX_LINKS)
|
|
break;
|
|
}
|
|
}
|
|
/* Check if the next char is the start of a nickname */
|
|
else if ((int) *PtrSrc == (int) '@')
|
|
{
|
|
Links[NumLinks].PtrStart = PtrSrc;
|
|
|
|
/* Find nickname end */
|
|
PtrSrc++; // Points to first character after @
|
|
|
|
/* A nick can have digits, letters and '_' */
|
|
for (;
|
|
*PtrSrc;
|
|
PtrSrc++)
|
|
if (!((*PtrSrc >= 'a' && *PtrSrc <= 'z') ||
|
|
(*PtrSrc >= 'A' && *PtrSrc <= 'Z') ||
|
|
(*PtrSrc >= '0' && *PtrSrc <= '9') ||
|
|
(*PtrSrc == '_')))
|
|
break;
|
|
|
|
/* Calculate length of this nickname */
|
|
Links[NumLinks].PtrEnd = PtrSrc - 1;
|
|
Links[NumLinks].NumActualBytes = (size_t) (PtrSrc - Links[NumLinks].PtrStart);
|
|
|
|
/* A nick (without arroba) must have a number of characters
|
|
Nck_MIN_BYTES_NICKNAME_WITHOUT_ARROBA <= Length <= Nck_MAX_BYTES_NICKNAME_WITHOUT_ARROBA */
|
|
Length = Links[NumLinks].NumActualBytes - 1; // Do not count the initial @
|
|
IsNickname = (Length >= Nck_MIN_BYTES_NICKNAME_WITHOUT_ARROBA &&
|
|
Length <= Nck_MAX_BYTES_NICKNAME_WITHOUT_ARROBA);
|
|
|
|
if (IsNickname)
|
|
{
|
|
/* Initialize anchors for this link */
|
|
Links[NumLinks].Anchor1Nick = NULL;
|
|
Links[NumLinks].Anchor2Nick = NULL;
|
|
|
|
/* Create id for this form */
|
|
Gbl.Form.Num++;
|
|
if (Gbl.Usrs.Me.Logged)
|
|
sprintf (Gbl.Form.UniqueId,"form_%s_%d",
|
|
Gbl.UniqueNameEncrypted,Gbl.Form.Num);
|
|
else
|
|
sprintf (Gbl.Form.Id,"form_%d",Gbl.Form.Num);
|
|
|
|
/* Store first part of anchor */
|
|
Act_SetParamsForm (ParamsStr,ActSeeOthPubPrf,true);
|
|
sprintf (Anchor1Nick,"<form method=\"post\" action=\"%s/%s\" id=\"%s\">"
|
|
"%s"
|
|
"<input type=\"hidden\" name=\"usr\" value=\"",
|
|
Cfg_URL_SWAD_CGI,
|
|
Txt_STR_LANG_ID[Gbl.Prefs.Language],
|
|
Gbl.Usrs.Me.Logged ? Gbl.Form.UniqueId :
|
|
Gbl.Form.Id,
|
|
ParamsStr);
|
|
Anchor1NickLength = strlen (Anchor1Nick);
|
|
if ((Links[NumLinks].Anchor1Nick = (char *) malloc (Anchor1NickLength + 1)) == NULL)
|
|
Lay_ShowErrorAndExit ("Not enough memory to insert link.");
|
|
strcpy (Links[NumLinks].Anchor1Nick,Anchor1Nick);
|
|
Links[NumLinks].Anchor1NickLength = Anchor1NickLength;
|
|
|
|
/* Store second part of anchor */
|
|
sprintf (Anchor2Nick,"\">"
|
|
"<a href=\"\""
|
|
" onclick=\"document.getElementById('%s').submit();"
|
|
"return false;\">",
|
|
Gbl.Usrs.Me.Logged ? Gbl.Form.UniqueId :
|
|
Gbl.Form.Id);
|
|
Anchor2NickLength = strlen (Anchor2Nick);
|
|
if ((Links[NumLinks].Anchor2Nick = (char *) malloc (Anchor2NickLength + 1)) == NULL)
|
|
Lay_ShowErrorAndExit ("Not enough memory to insert link.");
|
|
strcpy (Links[NumLinks].Anchor2Nick,Anchor2Nick);
|
|
Links[NumLinks].Anchor2NickLength = Anchor2NickLength;
|
|
|
|
AnchorNickTotalLength = Anchor1NickLength + Anchor2NickLength + Anchor3NickLength;
|
|
|
|
LengthVisibleLink = Links[NumLinks].NumActualBytes;
|
|
if (NumLinks == 0)
|
|
Links[NumLinks].AddedLengthUntilHere = AnchorNickTotalLength + LengthVisibleLink;
|
|
else
|
|
Links[NumLinks].AddedLengthUntilHere = Links[NumLinks - 1].AddedLengthUntilHere +
|
|
AnchorNickTotalLength + LengthVisibleLink;
|
|
|
|
/* Increment number of found links */
|
|
NumLinks++;
|
|
if (NumLinks == MAX_LINKS)
|
|
break;
|
|
}
|
|
}
|
|
/* The next char is not the start of URL or nickname */
|
|
else // Character distinct to 'h' or '@'
|
|
PtrSrc++;
|
|
|
|
/**********************************************************************/
|
|
/***** If there are one or more links (URLs or nicknames) in text *****/
|
|
/**********************************************************************/
|
|
if (NumLinks)
|
|
{
|
|
/***** Insert links from end to start of text,
|
|
only if there is enough space available in text *****/
|
|
TxtLengthWithInsertedAnchors = TxtLength + Links[NumLinks - 1].AddedLengthUntilHere;
|
|
if (TxtLengthWithInsertedAnchors <= MaxLength)
|
|
for (NumLink = NumLinks - 1;
|
|
NumLink >= 0;
|
|
NumLink--)
|
|
{
|
|
IsNickname = (*(Links[NumLink].PtrStart) == '@');
|
|
|
|
/***** Step 1: Move forward the text after the link (URL or nickname)
|
|
(it's mandatory to do the copy in reverse order
|
|
to avoid overwriting source) *****/
|
|
for (i = 0,
|
|
PtrSrc = (NumLink == NumLinks - 1) ? Txt + TxtLength :
|
|
Links[NumLink + 1].PtrStart - 1,
|
|
PtrDst = PtrSrc + Links[NumLink].AddedLengthUntilHere,
|
|
Length = PtrSrc - Links[NumLink].PtrEnd;
|
|
i < Length;
|
|
i++)
|
|
*PtrDst-- = *PtrSrc--;
|
|
|
|
/***** Step 2: Insert ANCHOR_3_NICK or ANCHOR_3_URL *****/
|
|
if (IsNickname)
|
|
{
|
|
Length = Anchor3NickLength;
|
|
PtrSrc = ANCHOR_3_NICK + Length - 1;
|
|
}
|
|
else
|
|
{
|
|
Length = Anchor3URLLength;
|
|
PtrSrc = ANCHOR_3_URL + Length - 1;
|
|
}
|
|
for (i = 0;
|
|
i < Length;
|
|
i++)
|
|
*PtrDst-- = *PtrSrc--;
|
|
|
|
/***** Step 3: Move forward the link (URL or nickname)
|
|
to be shown on screen *****/
|
|
if (IsNickname ||
|
|
Links[NumLink].NumActualBytes <= MaxCharsURLOnScreen)
|
|
{
|
|
NumBytesToShow = Links[NumLink].NumActualBytes;
|
|
PtrSrc = Links[NumLink].PtrEnd; // PtrSrc must point to end of complete nickname
|
|
}
|
|
else // If URL is too long to be displayed ==> short it
|
|
{
|
|
/* Make a copy of this URL */
|
|
NumBytesToCopy = (Links[NumLink].NumActualBytes < MAX_BYTES_LIMITED_URL) ? Links[NumLink].NumActualBytes :
|
|
MAX_BYTES_LIMITED_URL;
|
|
strncpy (LimitedURL,Links[NumLink].PtrStart,NumBytesToCopy);
|
|
LimitedURL[NumBytesToCopy] = '\0';
|
|
|
|
/* Limit the length of the copy */
|
|
NumBytesToShow = Str_LimitLengthHTMLStr (LimitedURL,MaxCharsURLOnScreen);
|
|
|
|
PtrSrc = LimitedURL + NumBytesToShow - 1; // PtrSrc must point to end of limited URL
|
|
}
|
|
for (i = 0;
|
|
i < NumBytesToShow;
|
|
i++)
|
|
*PtrDst-- = *PtrSrc--;
|
|
|
|
/***** Step 4: Insert Anchor2Nick or ANCHOR_2_URL *****/
|
|
if (IsNickname)
|
|
{
|
|
Length = Links[NumLink].Anchor2NickLength;
|
|
PtrSrc = Links[NumLink].Anchor2Nick + Length - 1;
|
|
}
|
|
else
|
|
{
|
|
Length = Anchor2URLLength;
|
|
PtrSrc = ANCHOR_2_URL + Length - 1;
|
|
}
|
|
for (i = 0;
|
|
i < Length;
|
|
i++)
|
|
*PtrDst-- = *PtrSrc--;
|
|
|
|
/***** Step 5: Insert link into directive A
|
|
(it's mandatory to do the copy in reverse order
|
|
to avoid overwriting source URL or nickname) *****/
|
|
for (i = 0, PtrSrc = Links[NumLink].PtrEnd;
|
|
i < Links[NumLink].NumActualBytes;
|
|
i++)
|
|
*PtrDst-- = *PtrSrc--;
|
|
|
|
/***** Step 6: Insert Anchor1Nick or ANCHOR_1_URL *****/
|
|
if (IsNickname)
|
|
{
|
|
Length = Links[NumLink].Anchor1NickLength;
|
|
PtrSrc = Links[NumLink].Anchor1Nick + Length - 1;
|
|
}
|
|
else
|
|
{
|
|
Length = Anchor1URLLength;
|
|
PtrSrc = ANCHOR_1_URL + Length - 1;
|
|
}
|
|
for (i = 0;
|
|
i < Length;
|
|
i++)
|
|
*PtrDst-- = *PtrSrc--;
|
|
}
|
|
}
|
|
|
|
/***********************************/
|
|
/***** Free memory for anchors *****/
|
|
/***********************************/
|
|
for (NumLink = 0;
|
|
NumLink < NumLinks;
|
|
NumLink++)
|
|
{
|
|
if (Links[NumLink].Anchor1Nick)
|
|
free ((void *) Links[NumLink].Anchor1Nick);
|
|
if (Links[NumLink].Anchor2Nick)
|
|
free ((void *) Links[NumLink].Anchor2Nick);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/** Get next ASCII character from a string converting &#number; to character */
|
|
/*****************************************************************************/
|
|
// Returns number of char analyzed
|
|
|
|
static unsigned Str_GetNextASCIICharFromStr (const char *Ptr,unsigned char *Ch)
|
|
{
|
|
unsigned NumChars;
|
|
unsigned Num;
|
|
|
|
if (*Ptr == '\0')
|
|
{
|
|
*Ch = '\0';
|
|
return 0;
|
|
}
|
|
else if (*Ptr == '&') // It's a &#num; character?
|
|
{
|
|
Ptr++;
|
|
if (*Ptr == '#')
|
|
{
|
|
Ptr++;
|
|
for (NumChars = 2, Num = 0;
|
|
*Ptr >= '0' && *Ptr <= '9';
|
|
Ptr++, NumChars++)
|
|
if (Num < 100000) // To avoid overflow
|
|
Num = Num * 10 + (unsigned) (*Ptr - '0');
|
|
if (*Ptr == ';') // &#num; found
|
|
{
|
|
NumChars++;
|
|
switch (Num)
|
|
{
|
|
case 32: *Ch = ' '; return NumChars;
|
|
case 33: *Ch = '!'; return NumChars;
|
|
case 34: *Ch = '\"'; return NumChars;
|
|
case 35: *Ch = '#'; return NumChars;
|
|
case 36: *Ch = '$'; return NumChars;
|
|
case 37: *Ch = '%'; return NumChars;
|
|
case 38: *Ch = '&'; return NumChars;
|
|
case 39: *Ch = '\''; return NumChars;
|
|
case 40: *Ch = '('; return NumChars;
|
|
case 41: *Ch = ')'; return NumChars;
|
|
case 42: *Ch = '*'; return NumChars;
|
|
case 43: *Ch = '+'; return NumChars;
|
|
case 44: *Ch = ','; return NumChars;
|
|
case 45: *Ch = '-'; return NumChars;
|
|
case 46: *Ch = '.'; return NumChars;
|
|
case 47: *Ch = '/'; return NumChars;
|
|
case 58: *Ch = ':'; return NumChars;
|
|
case 59: *Ch = ';'; return NumChars;
|
|
case 60: *Ch = ' '; return NumChars; // '<'
|
|
case 61: *Ch = '='; return NumChars;
|
|
case 62: *Ch = ' '; return NumChars; // '>'
|
|
case 63: *Ch = '?'; return NumChars;
|
|
case 64: *Ch = '@'; return NumChars;
|
|
case 91: *Ch = '['; return NumChars;
|
|
case 92: *Ch = '\\'; return NumChars;
|
|
case 93: *Ch = ']'; return NumChars;
|
|
case 94: *Ch = '^'; return NumChars;
|
|
case 95: *Ch = '_'; return NumChars;
|
|
case 96: *Ch = '`'; return NumChars;
|
|
case 123: *Ch = '{'; return NumChars;
|
|
case 124: *Ch = '|'; return NumChars;
|
|
case 125: *Ch = '}'; return NumChars;
|
|
case 126: *Ch = '~'; return NumChars;
|
|
default: *Ch = '?'; return NumChars; // Unknown character
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*Ch = (unsigned char) *Ptr;
|
|
return NumChars;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*Ch = (unsigned char) *Ptr;
|
|
return 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*Ch = (unsigned char) *Ptr;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Limit number of chars on screen of a string *****************/
|
|
/*****************************************************************************/
|
|
// Returns length of resulting string in bytes (a special char counts for several bytes)
|
|
|
|
size_t Str_LimitLengthHTMLStr (char *Str,size_t MaxCharsOnScreen)
|
|
{
|
|
char *Ptr;
|
|
size_t NumCharsOnScreen;
|
|
size_t Length;
|
|
size_t LengthHTMLEntity;
|
|
|
|
if (MaxCharsOnScreen < 3)
|
|
MaxCharsOnScreen = 3; // Length of "..."
|
|
|
|
/***** Calculate length counting "&...;" as one character *****/
|
|
for (Ptr = Str, NumCharsOnScreen = 0, Length = 0;
|
|
*Ptr;
|
|
Ptr++, NumCharsOnScreen++, Length++)
|
|
/* Check if an HTML entity is present */
|
|
if (*Ptr == '&') // Possible HTML entity
|
|
if ((LengthHTMLEntity = Str_FindHTMLEntity (Ptr)))
|
|
{
|
|
/* if Ptr points to ñ ==> Length = 8 */
|
|
Ptr += LengthHTMLEntity - 1; // Now Ptr point to ';'
|
|
Length += LengthHTMLEntity - 1;
|
|
}
|
|
|
|
if (NumCharsOnScreen <= MaxCharsOnScreen) // Don't limit string
|
|
return Length;
|
|
|
|
/***** Limit length of string *****/
|
|
for (Ptr = Str, NumCharsOnScreen = 0, Length = 0;
|
|
*Ptr;
|
|
Ptr++, NumCharsOnScreen++, Length++)
|
|
{
|
|
if (NumCharsOnScreen == MaxCharsOnScreen - 3)
|
|
{
|
|
*Ptr++ = '.';
|
|
*Ptr++ = '.';
|
|
*Ptr++ = '.';
|
|
*Ptr = '\0'; // ...limit length...
|
|
Length += 3;
|
|
break;
|
|
}
|
|
/* Check if an HTML entity or directive is present */
|
|
if (*Ptr == '&') // Possible HTML entity
|
|
{
|
|
if ((LengthHTMLEntity = Str_FindHTMLEntity (Ptr)))
|
|
{
|
|
/* if Ptr points to ñ ==> Length = 8 */
|
|
Ptr += LengthHTMLEntity - 1; // Now Ptr point to ';'
|
|
Length += LengthHTMLEntity - 1;
|
|
}
|
|
}
|
|
else if (*Ptr == '<') // HTML directive "<...>"
|
|
for (Ptr++, Length++;
|
|
*Ptr && *Ptr != '>';
|
|
Ptr++, Length++); // While not end of HTML directive "<...>"
|
|
}
|
|
|
|
return Length;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******** Return the length of a possible HTML entity inside a string ********/
|
|
/*****************************************************************************/
|
|
// For example, if Ptr points to "ñ..." or "面...", return 8
|
|
// If Ptr points to no HTML entity, return 0
|
|
|
|
static unsigned Str_FindHTMLEntity (const char *Ptr)
|
|
{
|
|
size_t Length = 0;
|
|
char Ch;
|
|
|
|
/***** The first character must be '&' *****/
|
|
if (Ptr[Length] != '&')
|
|
return 0; // No HTML entity found
|
|
|
|
/***** The second character can be '#' *****/
|
|
Length++;
|
|
if (Ptr[Length] == '#')
|
|
/* Go to third character */
|
|
Length++;
|
|
|
|
/***** Now one or more alphanumeric characters are expected *****/
|
|
/* Check second/third character */
|
|
Ch = Ptr[Length];
|
|
if (!((Ch >= '0' && Ch <= '9') ||
|
|
(Ch >= 'a' && Ch <= 'z') ||
|
|
(Ch >= 'A' && Ch <= 'Z')))
|
|
return 0; // No HTML entity found
|
|
|
|
/* Go to first non alphanumeric character */
|
|
do
|
|
{
|
|
Length++;
|
|
Ch = Ptr[Length];
|
|
}
|
|
while ((Ch >= '0' && Ch <= '9') ||
|
|
(Ch >= 'a' && Ch <= 'z') ||
|
|
(Ch >= 'A' && Ch <= 'Z'));
|
|
|
|
/***** An HTML entity must end by ';' *****/
|
|
return (Ptr[Length] == ';') ? Length + 1 : // HTML entity found (return Length including the final ';')
|
|
0; // No HTML entity found
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/**************** Check if a URL adreess looks as** valid ********************/
|
|
/*****************************************************************************/
|
|
/*
|
|
bool Str_URLLooksValid (const char *URL)
|
|
{
|
|
***** If it's a NULL pointer *****
|
|
if (!URL)
|
|
return false;
|
|
|
|
***** If it's the empty string *****
|
|
if (!URL[0])
|
|
return false;
|
|
|
|
***** Check if start by http:// or https:// *****
|
|
if (!strncasecmp (URL,"http://",7) || !strncasecmp (URL,"https://",8))
|
|
return (bool) (strchr (URL,(int) '.') != NULL); // There is any . in the URL
|
|
else // There's no http:// nor https://
|
|
return false;
|
|
}
|
|
*/
|
|
/*****************************************************************************/
|
|
/***** Convert a string to title: first uppercase and the rest lowercase *****/
|
|
/*****************************************************************************/
|
|
|
|
void Str_ConvertToTitleType (char *Str)
|
|
{
|
|
char Ch;
|
|
char *Ptr;
|
|
char *Ptr2;
|
|
unsigned LengthStr;
|
|
bool FirstLetter = true;
|
|
|
|
for (Ptr = Str;
|
|
*Ptr;
|
|
Ptr++)
|
|
{
|
|
Ch = *Ptr;
|
|
if (isspace ((int) Ch) ||
|
|
Ch == '\xA0' || // Unicode translation for
|
|
Ch == '-' ||
|
|
Ch == '(' ||
|
|
Ch == ')' ||
|
|
Ch == ',' ||
|
|
// Ch == ';' || // Do not start a new word on ';'.
|
|
// Example: aktoğan should be converted to Aktoğan
|
|
Ch == '.' ||
|
|
Ch == ':' ||
|
|
Ch == 'ª' ||
|
|
Ch == 'º')
|
|
FirstLetter = true;
|
|
else
|
|
{
|
|
if (FirstLetter)
|
|
{
|
|
/* Check if it's "de", "del", "la", "y" */ // This should be internationalized!!!!!
|
|
for (Ptr2 = Ptr, LengthStr = 0;
|
|
*Ptr2;
|
|
Ptr2++, LengthStr++)
|
|
if (isspace ((int) *Ptr2) ||
|
|
*Ptr2 == '\xA0') // Unicode translation for
|
|
break;
|
|
if (LengthStr == 1)
|
|
{
|
|
if (!strncasecmp (Ptr,"y",1))
|
|
FirstLetter = false;
|
|
}
|
|
else if (LengthStr == 2)
|
|
{
|
|
if (!strncasecmp (Ptr,"de",2))
|
|
FirstLetter = false;
|
|
else if (!strncasecmp (Ptr,"la",2))
|
|
FirstLetter = false;
|
|
}
|
|
else if (LengthStr == 3)
|
|
if (!strncasecmp (Ptr,"del",3))
|
|
FirstLetter = false;
|
|
}
|
|
if (FirstLetter)
|
|
{
|
|
*Ptr = Str_ConvertToUpperLetter (*Ptr);
|
|
FirstLetter = false;
|
|
}
|
|
else
|
|
*Ptr = Str_ConvertToLowerLetter (*Ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*** Changes a string to made it comparable with others (removing tildes) ****/
|
|
/*****************************************************************************/
|
|
|
|
void Str_ConvertToComparable (char *Str)
|
|
{
|
|
char *Ptr;
|
|
|
|
for (Ptr = Str;
|
|
*Ptr;
|
|
Ptr++)
|
|
{
|
|
*Ptr = Str_ConvertToLowerLetter (*Ptr);
|
|
switch (*Ptr)
|
|
{
|
|
case 'á': *Ptr = 'a'; break;
|
|
case 'é': *Ptr = 'e'; break;
|
|
case 'í': *Ptr = 'i'; break;
|
|
case 'ó': *Ptr = 'o'; break;
|
|
case 'ú': *Ptr = 'u'; break;
|
|
case 'ä': *Ptr = 'a'; break;
|
|
case 'ë': *Ptr = 'e'; break;
|
|
case 'ï': *Ptr = 'i'; break;
|
|
case 'ö': *Ptr = 'o'; break;
|
|
case 'ü': *Ptr = 'u'; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Convert a string to uppercase ************************/
|
|
/*****************************************************************************/
|
|
|
|
char *Str_ConvertToUpperText (char *Str)
|
|
{
|
|
char *Ptr;
|
|
|
|
for (Ptr = Str;
|
|
*Ptr;
|
|
Ptr++)
|
|
*Ptr = Str_ConvertToUpperLetter (*Ptr);
|
|
|
|
return Str;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Convert a string to lowercase ************************/
|
|
/*****************************************************************************/
|
|
|
|
char *Str_ConvertToLowerText (char *Str)
|
|
{
|
|
char *Ptr;
|
|
|
|
for (Ptr = Str;
|
|
*Ptr;
|
|
Ptr++)
|
|
*Ptr = Str_ConvertToLowerLetter (*Ptr);
|
|
|
|
return Str;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Convert a character to uppercase ********************/
|
|
/*****************************************************************************/
|
|
|
|
char Str_ConvertToUpperLetter (char Ch)
|
|
{
|
|
switch (Ch)
|
|
{
|
|
case 'á': return 'Á';
|
|
case 'é': return 'É';
|
|
case 'í': return 'Í';
|
|
case 'ó': return 'Ó';
|
|
case 'ú': return 'Ú';
|
|
case 'ñ': return 'Ñ';
|
|
case 'ç': return 'Ç';
|
|
case 'ä': return 'Ä';
|
|
case 'ë': return 'Ë';
|
|
case 'ï': return 'Ï';
|
|
case 'ö': return 'Ö';
|
|
case 'ü': return 'Ü';
|
|
default: return (char) toupper ((int) Ch);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Convert a character to lowercase ********************/
|
|
/*****************************************************************************/
|
|
|
|
char Str_ConvertToLowerLetter (char Ch)
|
|
{
|
|
switch (Ch)
|
|
{
|
|
case 'Á': return 'á';
|
|
case 'É': return 'é';
|
|
case 'Í': return 'í';
|
|
case 'Ó': return 'ó';
|
|
case 'Ú': return 'ú';
|
|
case 'Ñ': return 'ñ';
|
|
case 'Ç': return 'ç';
|
|
case 'Ä': return 'ä';
|
|
case 'Ë': return 'ë';
|
|
case 'Ï': return 'ï';
|
|
case 'Ö': return 'ö';
|
|
case 'Ü': return 'ü';
|
|
default: return (char) tolower ((int) Ch);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******** Write a number in floating point with the correct accuracy *********/
|
|
/*****************************************************************************/
|
|
|
|
void Str_WriteFloatNum (FILE *FileDst,float Number)
|
|
{
|
|
double IntegerPart;
|
|
double FractionaryPart;
|
|
char *Format;
|
|
|
|
FractionaryPart = modf ((double) Number,&IntegerPart);
|
|
|
|
if (FractionaryPart == 0.0)
|
|
fprintf (FileDst,"%.0f",IntegerPart);
|
|
else
|
|
{
|
|
if (IntegerPart != 0.0 || FractionaryPart >= 0.1)
|
|
Format = "%.2f";
|
|
else if (FractionaryPart >= 0.01)
|
|
Format = "%.3f";
|
|
else if (FractionaryPart >= 0.001)
|
|
Format = "%.4f";
|
|
else if (FractionaryPart >= 0.0001)
|
|
Format = "%.5f";
|
|
else if (FractionaryPart >= 0.00001)
|
|
Format = "%.6f";
|
|
else if (FractionaryPart >= 0.000001)
|
|
Format = "%.7f";
|
|
else
|
|
Format = "%e";
|
|
fprintf (FileDst,Format,Number);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********** Convert a string that holds a number in floating comma ***********/
|
|
/********** to another in floating point changing commas to points ***********/
|
|
/*****************************************************************************/
|
|
|
|
void Str_ConvertStrFloatCommaToStrFloatPoint (char *Str)
|
|
{
|
|
for (;
|
|
*Str;
|
|
Str++)
|
|
if (*Str == ',')
|
|
*Str = '.';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Read a number in floating point from a string ****************/
|
|
/*****************************************************************************/
|
|
|
|
float Str_GetFloatNumFromStr (const char *Str)
|
|
{
|
|
float Num;
|
|
|
|
if (Str)
|
|
{
|
|
Str_SetDecimalPointToUS (); // To get the decimal point as a dot
|
|
if (sscanf (Str,"%f",&Num) != 1)
|
|
Lay_ShowErrorAndExit ("Bad floating point format.");
|
|
Str_SetDecimalPointToLocal (); // Return to local system
|
|
}
|
|
else // Str == NULL
|
|
Num = 0.0;
|
|
|
|
return Num;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/**** Change decimal point to US system in order to get/print it as a dot ****/
|
|
/*****************************************************************************/
|
|
|
|
void Str_SetDecimalPointToUS (void)
|
|
{
|
|
if (!setlocale (LC_NUMERIC,"en_US.utf8")) // To get/print the floating point as a dot
|
|
if (Gbl.Layout.HTMLStartWritten)
|
|
Ale_ShowAlert (Ale_ERROR,"Can not set locale to en_US.");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************** Change decimal point to local system *********************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_SetDecimalPointToLocal (void)
|
|
{
|
|
// TODO: this should be internationalized!!!!!!!
|
|
if (!setlocale (LC_NUMERIC,"es_ES.utf8")) // Return to local system
|
|
if (Gbl.Layout.HTMLStartWritten)
|
|
Ale_ShowAlert (Ale_ERROR,"Can not set locale to es_ES.");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Add a string to a Query, changing ' for ' ***************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_AddStrToQuery (char *Query,const char *Str,size_t SizeOfQuery)
|
|
{
|
|
size_t LengthOfQuery = strlen (Query);
|
|
size_t SizeOfQuery_6 = SizeOfQuery - 6;
|
|
size_t SizeOfQuery_2 = SizeOfQuery - 2;
|
|
|
|
// Query can not have a ' character
|
|
for (;
|
|
*Str;
|
|
Str++)
|
|
if (*Str == '\'') // It's a ' ...
|
|
{
|
|
if (LengthOfQuery > SizeOfQuery_6)
|
|
break;
|
|
Query[LengthOfQuery++] = '&';
|
|
Query[LengthOfQuery++] = '#';
|
|
Query[LengthOfQuery++] = '3';
|
|
Query[LengthOfQuery++] = '9';
|
|
Query[LengthOfQuery++] = ';'; // ...add the HTML special character '
|
|
}
|
|
else // It's another character...
|
|
{
|
|
if (LengthOfQuery > SizeOfQuery_2)
|
|
break;
|
|
Query[LengthOfQuery++] = *Str; // ...add the character without changes
|
|
}
|
|
Query[LengthOfQuery] = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Change the format of a string **************************/
|
|
/*****************************************************************************/
|
|
/*
|
|
ChangeFrom can be:
|
|
Str_FROM_FORM
|
|
Str_FROM_TEXT
|
|
Str_FROM_HTML
|
|
ChangeTo can be:
|
|
Str_DONT_CHANGE
|
|
Str_TO_TEXT
|
|
Str_TO_MARKDOWN
|
|
Str_TO_HTML
|
|
Str_TO_RIGOROUS_HTML
|
|
For example the string "Nueva++de+San+Ant%F3n"
|
|
is converted to:
|
|
"Nueva de San Antón" if ChangeTo == Str_TO_RIGOROUS_HTML
|
|
"Nueva de San Antón" if ChangeTo == Str_TO_HTML
|
|
"Nueva de San Antón" if ChangeTo == Str_TO_TEXT
|
|
*/
|
|
#define Str_MAX_BYTES_SPECIAL_CHAR (256 - 1)
|
|
|
|
void Str_ChangeFormat (Str_ChangeFrom_t ChangeFrom,Str_ChangeTo_t ChangeTo,
|
|
char *Str,size_t MaxLengthStr,bool RemoveLeadingAndTrailingSpaces)
|
|
{
|
|
char *StrDst;
|
|
char *PtrSrc;
|
|
char *PtrDst;
|
|
unsigned char Ch;
|
|
unsigned int SpecialChar;
|
|
size_t LengthSpecStrSrc = 0;
|
|
size_t LengthSpecStrDst;
|
|
unsigned long LengthStrDst = 0;
|
|
unsigned NumSpacesTab;
|
|
unsigned i;
|
|
unsigned NumPrintableCharsFromReturn = 0; // To substitute tabs for spaces
|
|
bool IsSpecialChar = false;
|
|
bool ThereIsSpaceChar = true; // Indicates if the character before was a space. Set to true to respect the initial spaces.
|
|
char StrSpecialChar[Str_MAX_BYTES_SPECIAL_CHAR + 1];
|
|
|
|
/*
|
|
if (Gbl.Usrs.Me.Roles.LoggedRole == Rol_SYS_ADM)
|
|
{
|
|
sprintf (Gbl.Alert.Txt,"Str_ChangeFormat ("%s")",Str);
|
|
Lay_ShowAlert (Lay_INFO,Gbl.Alert.Txt);
|
|
}
|
|
*/
|
|
|
|
if (ChangeTo != Str_DONT_CHANGE)
|
|
{
|
|
/***** Allocate memory for a destination string where to do the changes *****/
|
|
if ((StrDst = (char *) malloc (MaxLengthStr + 1)) == NULL)
|
|
Lay_ShowErrorAndExit ("Not enough memory to change the format of a string.");
|
|
|
|
/***** Make the change *****/
|
|
for (PtrSrc = Str, PtrDst = StrDst;
|
|
*PtrSrc;)
|
|
{
|
|
Ch = (unsigned char) *PtrSrc;
|
|
switch (ChangeFrom)
|
|
{
|
|
case Str_FROM_FORM:
|
|
if (Gbl.ContentReceivedByCGI == Act_CONT_DATA)
|
|
// The form contained data and was sent with content type multipart/form-data
|
|
switch (Ch)
|
|
{
|
|
case 0x20: /* Space */
|
|
case 0x22: /* Change double comilla --> """ */
|
|
case 0x23: /* '#' */
|
|
case 0x26: /* Change '&' --> "&" */
|
|
case 0x27: /* Change single comilla --> "'" to avoid SQL code injection */
|
|
case 0x2C: /* ',' */
|
|
case 0x2F: /* '/' */
|
|
case 0x3A: /* ':' */
|
|
case 0x3B: /* ';' */
|
|
case 0x3C: /* '<' --> "<" */
|
|
case 0x3E: /* '>' --> ">" */
|
|
case 0x3F: /* '?' */
|
|
case 0x40: /* '@' */
|
|
case 0x5C: /* '\\' */
|
|
IsSpecialChar = true;
|
|
LengthSpecStrSrc = 1;
|
|
SpecialChar = (unsigned int) Ch;
|
|
break;
|
|
default:
|
|
if (Ch < 0x20 || Ch > 0x7F)
|
|
{
|
|
IsSpecialChar = true;
|
|
LengthSpecStrSrc = 1;
|
|
SpecialChar = (unsigned int) Ch;
|
|
}
|
|
else
|
|
{
|
|
IsSpecialChar = false;
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
}
|
|
break;
|
|
}
|
|
else // Gbl.ContentReceivedByCGI == Act_CONTENT_NORM
|
|
// The form contained text and was sent with content type application/x-www-form-urlencoded
|
|
switch (Ch)
|
|
{
|
|
case '+': /* Change every '+' to a space */
|
|
IsSpecialChar = true;
|
|
LengthSpecStrSrc = 1;
|
|
SpecialChar = 0x20;
|
|
break;
|
|
case '%': /* Change "%XX" --> "&#decimal_number;" (from 0 to 255) */
|
|
IsSpecialChar = true;
|
|
/* Some special characters, like a chinese character,
|
|
are received from a form in a format like this:
|
|
%26%2335753%3B --> %26 %23 3 5 7 5 3 %3B --> 让
|
|
^ ^ ^
|
|
| | |
|
|
SpecialChar SpecialChar SpecialChar
|
|
Here one chinese character is converted
|
|
to 2 special chars + 5 normal chars + 1 special char,
|
|
and finally is stored as the following 8 bytes: 让
|
|
*/
|
|
sscanf (PtrSrc + 1,"%2X",&SpecialChar);
|
|
LengthSpecStrSrc = 3;
|
|
break;
|
|
case 0x27: /* Change single comilla --> "'" to avoid SQL code injection */
|
|
case 0x5C: /* '\\' */
|
|
IsSpecialChar = true;
|
|
LengthSpecStrSrc = 1;
|
|
SpecialChar = (unsigned int) Ch;
|
|
break;
|
|
default:
|
|
IsSpecialChar = false;
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
}
|
|
break;
|
|
case Str_FROM_HTML:
|
|
case Str_FROM_TEXT:
|
|
switch (Ch)
|
|
{
|
|
case 0x20: /* Space */
|
|
case 0x27: /* Change single comilla --> "'" to avoid SQL code injection */
|
|
case 0x5C: /* '\\' */
|
|
IsSpecialChar = true;
|
|
LengthSpecStrSrc = 1;
|
|
SpecialChar = (unsigned int) Ch;
|
|
break;
|
|
default:
|
|
if (Ch < 0x20)
|
|
{
|
|
IsSpecialChar = true;
|
|
LengthSpecStrSrc = 1;
|
|
SpecialChar = (unsigned int) Ch;
|
|
}
|
|
else
|
|
{
|
|
IsSpecialChar = false;
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (IsSpecialChar)
|
|
{
|
|
switch (SpecialChar)
|
|
{
|
|
case 0x09: /* Tab */
|
|
if (ChangeTo == Str_TO_RIGOROUS_HTML)
|
|
{
|
|
/* Change tab to spaces (so many spaces as left until the next multiple of 8) */
|
|
StrSpecialChar[0] = '\0';
|
|
NumSpacesTab = 8 - NumPrintableCharsFromReturn % 8;
|
|
// Insert a space to separate former string from
|
|
// This space is not printed on screen in HTML,
|
|
// but it is necessary to mark the end of a possible previous URL
|
|
Str_Concat (StrSpecialChar,
|
|
ThereIsSpaceChar ? " " :
|
|
" ", // The first space
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
for (i = 1;
|
|
i < NumSpacesTab;
|
|
i++) // Rest of spaces, except the first
|
|
Str_Concat (StrSpecialChar," ", // Add a space
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
NumPrintableCharsFromReturn += NumSpacesTab;
|
|
}
|
|
else
|
|
{
|
|
StrSpecialChar[0] = '\t';
|
|
StrSpecialChar[1] = '\0';
|
|
}
|
|
ThereIsSpaceChar = true;
|
|
break;
|
|
case 0x0A: /* \n */
|
|
if (ChangeTo == Str_TO_RIGOROUS_HTML)
|
|
Str_Copy (StrSpecialChar,"<br />",
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
else
|
|
{
|
|
StrSpecialChar[0] = Str_LF[0];
|
|
StrSpecialChar[1] = '\0';
|
|
}
|
|
NumPrintableCharsFromReturn = 0;
|
|
ThereIsSpaceChar = true;
|
|
break;
|
|
case 0x0D: /* "%0D" --> "" */
|
|
if (ChangeTo == Str_TO_RIGOROUS_HTML)
|
|
StrSpecialChar[0] = '\0';
|
|
else
|
|
{
|
|
StrSpecialChar[0] = Str_CR[0];
|
|
StrSpecialChar[1] = '\0';
|
|
}
|
|
NumPrintableCharsFromReturn = 0;
|
|
ThereIsSpaceChar = true;
|
|
break;
|
|
case 0x20: /* Space */
|
|
if (ChangeTo == Str_TO_RIGOROUS_HTML && ThereIsSpaceChar)
|
|
Str_Copy (StrSpecialChar," ",
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
else
|
|
{
|
|
StrSpecialChar[0] = ' ';
|
|
StrSpecialChar[1] = '\0';
|
|
}
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = true;
|
|
break;
|
|
case 0x22: /* "%22" --> """ (double comilla) */
|
|
if (ChangeTo == Str_TO_MARKDOWN)
|
|
{ // Escape sequence for database, two characters
|
|
StrSpecialChar[0] = '\\'; // 1. An inverted bar
|
|
StrSpecialChar[1] = '\"'; // 2. A double comilla
|
|
StrSpecialChar[2] = '\0'; // End of string
|
|
}
|
|
else
|
|
sprintf (StrSpecialChar,"""); // Double comilla is stored as HTML code to avoid problems when displaying it
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x23: /* "%23" --> "#" */
|
|
StrSpecialChar[0] = '#'; // '#' must be converted to '#' to allow HTML entities like 分
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x26: /* "%26" --> "&" */
|
|
StrSpecialChar[0] = '&'; // '&' must be converted to '&' to allow HTML entities like 分
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x27: /* "%27" --> "'" (single comilla) */
|
|
if (ChangeTo == Str_TO_MARKDOWN)
|
|
{ // Escape sequence for database, two characters
|
|
StrSpecialChar[0] = '\\'; // 1. An inverted bar
|
|
StrSpecialChar[1] = '\''; // 2. A single comilla
|
|
StrSpecialChar[2] = '\0'; // End of string
|
|
}
|
|
else
|
|
sprintf (StrSpecialChar,"'"); // Single comilla is stored as HTML entity to avoid problem when querying database (SQL code injection)
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x2C: /* "%2C" --> "," */
|
|
StrSpecialChar[0] = ',';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x2F: /* "%2F" --> "/" */
|
|
StrSpecialChar[0] = '/';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x3A: /* "%3A" --> ":" */
|
|
StrSpecialChar[0] = ':';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x3B: /* "%3B" --> ";" */
|
|
StrSpecialChar[0] = ';'; // ';' must be converted to ';' to allow HTML entities like 分
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x3C: /* "%3C" --> "<" (<) */
|
|
if (ChangeTo == Str_TO_MARKDOWN)
|
|
{
|
|
StrSpecialChar[0] = '<';
|
|
StrSpecialChar[1] = '\0';
|
|
}
|
|
else
|
|
Str_Copy (StrSpecialChar,"<", // "<" is stored as HTML code to avoid problems when displaying it
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x3E: /* "%3E" --> ">" (>) */
|
|
if (ChangeTo == Str_TO_MARKDOWN)
|
|
{
|
|
StrSpecialChar[0] = '>';
|
|
StrSpecialChar[1] = '\0';
|
|
}
|
|
else
|
|
Str_Copy (StrSpecialChar,">", // ">" is stored as HTML code to avoid problems when displaying it
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x3F: /* "%3F" --> "?" */
|
|
StrSpecialChar[0] = '?';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x40: /* "%40" --> "@" */
|
|
StrSpecialChar[0] = '@';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0x5C: /* "%5C" --> "\" (\) */
|
|
if (ChangeTo == Str_TO_MARKDOWN)
|
|
{ // Escape sequence for database, two characters
|
|
StrSpecialChar[0] = '\\'; // 1. An inverted bar
|
|
StrSpecialChar[1] = '\\'; // 2. An inverted bar
|
|
StrSpecialChar[2] = '\0'; // End of string
|
|
}
|
|
else
|
|
Str_Copy (StrSpecialChar,"\", // "\" is stored as HTML code to avoid problems when displaying it
|
|
Str_MAX_BYTES_SPECIAL_CHAR);
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xC1: /* "%C1" --> "Á" */
|
|
StrSpecialChar[0] = 'Á';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xC9: /* "%C9" --> "É" */
|
|
StrSpecialChar[0] = 'É';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xCD: /* "%CD" --> "Í" */
|
|
StrSpecialChar[0] = 'Í';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xD3: /* "%D3" --> "Ó" */
|
|
StrSpecialChar[0] = 'Ó';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xDA: /* "%DA" --> "Ú" */
|
|
StrSpecialChar[0] = 'Ú';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xD1: /* "%D1" --> "Ñ" */
|
|
StrSpecialChar[0] = 'Ñ';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xC7: /* "%C7" --> "Ç" */
|
|
StrSpecialChar[0] = 'Ç';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xE1: /* "%E1" --> "á" */
|
|
StrSpecialChar[0] = 'á';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xE9: /* "%E9" --> "é" */
|
|
StrSpecialChar[0] = 'é';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xED: /* "%ED" --> "í" */
|
|
StrSpecialChar[0] = 'í';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xF3: /* "%F3" --> "ó" */
|
|
StrSpecialChar[0] = 'ó';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xFA: /* "%FA" --> "ú" */
|
|
StrSpecialChar[0] = 'ú';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xF1: /* "%F1" --> "ñ" */
|
|
StrSpecialChar[0] = 'ñ';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
case 0xE7: /* "%E7" --> "ç" */
|
|
StrSpecialChar[0] = 'ç';
|
|
StrSpecialChar[1] = '\0';
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
default: /* The rest of special chars are stored as special code */
|
|
sprintf (StrSpecialChar,
|
|
(ChangeTo == Str_TO_TEXT ||
|
|
ChangeTo == Str_TO_MARKDOWN) ? "%c" :
|
|
"&#%u;",
|
|
SpecialChar);
|
|
NumPrintableCharsFromReturn++;
|
|
ThereIsSpaceChar = false;
|
|
break;
|
|
}
|
|
/* Compute length of destination special char */
|
|
LengthSpecStrDst = strlen (StrSpecialChar);
|
|
|
|
/* Check new length of destination string after the copy */
|
|
if (LengthStrDst + LengthSpecStrDst > MaxLengthStr)
|
|
Lay_ShowErrorAndExit ("Space allocated to string is full.");
|
|
|
|
/* Copy to appropiate place the special character string */
|
|
strncpy (PtrDst,StrSpecialChar,LengthSpecStrDst);
|
|
|
|
/* Increment pointer to character after ';' */
|
|
PtrSrc += LengthSpecStrSrc;
|
|
PtrDst += LengthSpecStrDst;
|
|
|
|
/* Increment length of destination string */
|
|
LengthStrDst += LengthSpecStrDst;
|
|
}
|
|
else // Not a special char ==> copy char from source to destination
|
|
{
|
|
/* Check new length of destination string after the copy */
|
|
if (LengthStrDst >= MaxLengthStr)
|
|
Lay_ShowErrorAndExit ("Space allocated to string is full.");
|
|
|
|
/* Copy char from source to destination and increment pointers */
|
|
*PtrDst++ = *PtrSrc++;
|
|
|
|
/* Increment length of destination string */
|
|
LengthStrDst++;
|
|
}
|
|
}
|
|
StrDst[LengthStrDst] = '\0';
|
|
|
|
/***** Copy destination string with changes to source string *****/
|
|
strncpy (Str,StrDst,LengthStrDst); // Str <-- StrDst
|
|
Str[LengthStrDst] = '\0';
|
|
|
|
/***** Free memory used for the destination string *****/
|
|
free ((void *) StrDst);
|
|
}
|
|
|
|
if (RemoveLeadingAndTrailingSpaces)
|
|
{
|
|
/***** Remove leading spaces *****/
|
|
Str_RemoveLeadingSpacesHTML (Str);
|
|
|
|
/***** Remove trailing spaces *****/
|
|
Str_RemoveTrailingSpacesHTML (Str);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************** Remove the spaces iniciales of a string ******************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_RemoveLeadingSpacesHTML (char *Str)
|
|
{
|
|
char *Ptr;
|
|
|
|
/***** Find the first character no space from left to right *****/
|
|
for (Ptr = Str;
|
|
*Ptr;
|
|
Ptr++)
|
|
{
|
|
/* If it's space ==> continue in the loop */
|
|
if (isspace ((int) *Ptr) ||
|
|
*Ptr == '\xA0') // Unicode translation for
|
|
continue;
|
|
/* Check forward if it's a <br> or <br /> */
|
|
if (*Ptr == '<')
|
|
{
|
|
Ptr++;
|
|
if (tolower ((int) *Ptr) != 'b')
|
|
{
|
|
Ptr--;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (tolower ((int) *Ptr) != 'r')
|
|
{
|
|
Ptr -= 2;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (*Ptr == '>')
|
|
continue; // It's <br>
|
|
if (*Ptr != ' ')
|
|
{
|
|
Ptr -= 3;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (*Ptr != '/')
|
|
{
|
|
Ptr -= 4;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (*Ptr != '>')
|
|
{
|
|
Ptr -= 5;
|
|
break;
|
|
}
|
|
continue; // It's <br />
|
|
}
|
|
/* Check forward if it's */
|
|
if (*Ptr == '&')
|
|
{
|
|
Ptr++;
|
|
if (tolower ((int) *Ptr) != 'n')
|
|
{
|
|
Ptr--;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (tolower ((int) *Ptr) != 'b')
|
|
{
|
|
Ptr -= 2;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (tolower ((int) *Ptr) != 's')
|
|
{
|
|
Ptr -= 3;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (tolower ((int) *Ptr) != 'p')
|
|
{
|
|
Ptr -= 4;
|
|
break;
|
|
}
|
|
if (!*Ptr)
|
|
break;
|
|
Ptr++;
|
|
if (*Ptr != ';')
|
|
{
|
|
Ptr -= 5;
|
|
break;
|
|
}
|
|
/* It's */
|
|
continue;
|
|
}
|
|
/* It's not space */
|
|
break;
|
|
}
|
|
|
|
/***** Ptr is the first character no space *****/
|
|
if (Ptr != Str)
|
|
{
|
|
/* Copy string pointed by Ptr to Str.
|
|
Do not use strcpy because according to strcpy manual:
|
|
"The source and destination strings should not overlap,
|
|
as the behavior is undefined" */
|
|
while (*Ptr)
|
|
*Str++ = *Ptr++;
|
|
*Str = '\0';
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Remove the trailing spaces of a string ******************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_RemoveTrailingSpacesHTML (char *Str)
|
|
{
|
|
char *Ptr;
|
|
|
|
/* Find the first not space character starting at the end */
|
|
for (Ptr = Str + strlen (Str) - 1;
|
|
Ptr >= Str;
|
|
Ptr--)
|
|
{
|
|
/* If it's space ==> continue in the loop */
|
|
if (isspace ((int) *Ptr) ||
|
|
*Ptr == '\xA0') // Unicode translation for
|
|
continue;
|
|
/* Check backward if it's <br> or <br /> */
|
|
if (*Ptr == '>')
|
|
{
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (*Ptr == '/') // Possible <br />
|
|
{
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (*Ptr != ' ')
|
|
{
|
|
Ptr += 2;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 'r')
|
|
{
|
|
Ptr += 3;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 'b')
|
|
{
|
|
Ptr += 4;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (*Ptr != '<')
|
|
{
|
|
Ptr += 5;
|
|
break;
|
|
}
|
|
/* It's <br /> */
|
|
continue;
|
|
}
|
|
else // Possible <br>
|
|
{
|
|
if (tolower ((int) *Ptr) != 'r')
|
|
{
|
|
Ptr++;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 'b')
|
|
{
|
|
Ptr += 2;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (*Ptr != '<')
|
|
{
|
|
Ptr += 3;
|
|
break;
|
|
}
|
|
/* It's <br> */
|
|
continue;
|
|
}
|
|
}
|
|
/* Check backward if it's */
|
|
if (*Ptr == ';')
|
|
{
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 'p')
|
|
{
|
|
Ptr ++;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 's')
|
|
{
|
|
Ptr += 2;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 'b')
|
|
{
|
|
Ptr += 3;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (tolower ((int) *Ptr) != 'n')
|
|
{
|
|
Ptr += 4;
|
|
break;
|
|
}
|
|
if (Ptr == Str)
|
|
break;
|
|
Ptr--;
|
|
if (*Ptr != '&')
|
|
{
|
|
Ptr += 5;
|
|
break;
|
|
}
|
|
/* It's */
|
|
continue;
|
|
}
|
|
/* It's not space */
|
|
break;
|
|
}
|
|
|
|
*(Ptr + 1) = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********* Remove the leading zeros ('0', not '\0') from a string ************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_RemoveLeadingZeros (char *Str)
|
|
{
|
|
size_t NumLeadingZeros;
|
|
|
|
if (Str)
|
|
if (Str[0])
|
|
{
|
|
NumLeadingZeros = strspn (Str,"0");
|
|
if (NumLeadingZeros)
|
|
// Do not use strcpy / memcpy because the strings overlap
|
|
memmove (Str,&Str[NumLeadingZeros],
|
|
strlen (Str) - NumLeadingZeros + 1);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Delete @'s at the start of a string ***********************/
|
|
/*****************************************************************************/
|
|
|
|
#include "swad_database.h"
|
|
|
|
void Str_RemoveLeadingArrobas (char *Str)
|
|
{
|
|
size_t NumLeadingArrobas;
|
|
|
|
if (Str)
|
|
if (Str[0])
|
|
{
|
|
NumLeadingArrobas = strspn (Str,"@");
|
|
if (NumLeadingArrobas)
|
|
// Do not use strcpy / memcpy because the strings overlap
|
|
memmove (Str,&Str[NumLeadingArrobas],
|
|
strlen (Str) - NumLeadingArrobas + 1);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Find string in a HTML file skipping comments ****************/
|
|
/*****************************************************************************/
|
|
/*
|
|
Search (case insensitive) the string Str in HTML file FileSrc, skipping comments.
|
|
Returns true if found.
|
|
Returns false if not found.
|
|
*/
|
|
|
|
bool Str_FindStrInFile (FILE *FileSrc,const char *Str,Str_SkipHTMLComments_t SkipHTMLComments)
|
|
{
|
|
int i;
|
|
int Length = strlen (Str);
|
|
int Ch;
|
|
long CurPos;
|
|
|
|
for (;;)
|
|
{
|
|
/* Skip possible comments */
|
|
if ((Ch = Str_ReadCharAndSkipComments (FileSrc,SkipHTMLComments)) == EOF) // Set pointer to '<' if not comment, or to first character after comment if comment
|
|
return false;
|
|
CurPos = ftell (FileSrc);
|
|
|
|
if (Str_ConvertToLowerLetter ((char) Ch) == Str_ConvertToLowerLetter (Str[0])) // First char found
|
|
{
|
|
for (i = 1;
|
|
i < Length;
|
|
i++)
|
|
{
|
|
/* Skip possible comments */
|
|
if ((Ch = Str_ReadCharAndSkipComments (FileSrc,SkipHTMLComments)) == EOF) // Set pointer to '<' if not comment, or to first character after comment if comment
|
|
return false;
|
|
if (Str_ConvertToLowerLetter ((char) Ch) != Str_ConvertToLowerLetter (Str[i]))
|
|
break;
|
|
}
|
|
if (i == Length) // Found!
|
|
return true;
|
|
else // Not found, continue on next character
|
|
fseek (FileSrc,CurPos,SEEK_SET);
|
|
}
|
|
}
|
|
|
|
return false; // Not reached
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Search a string in a file backward **********************/
|
|
/*****************************************************************************/
|
|
|
|
/* The file queda posicionado in:
|
|
If found --> in the character anterior to the first of the string
|
|
If no found --> to the principio of the file */
|
|
|
|
bool Str_FindStrInFileBack (FILE *FileSrc,const char *Str,Str_SkipHTMLComments_t SkipHTMLComments)
|
|
{
|
|
int i;
|
|
int Length = strlen (Str);
|
|
int Ch;
|
|
int ChFinal;
|
|
long CurPos;
|
|
|
|
ChFinal = Str_ConvertToLowerLetter (Str[Length-1]);
|
|
if (fseek (FileSrc,-1L,SEEK_CUR)) // Go to the previous character
|
|
return false;
|
|
for (;;)
|
|
{
|
|
if ((Ch = Str_ReadCharAndSkipCommentsBackward (FileSrc,SkipHTMLComments)) == EOF) // Set pointer to '>' if not comment, or to character before start of comment if comment
|
|
return false;
|
|
CurPos = ftell (FileSrc);
|
|
|
|
if (Str_ConvertToLowerLetter ((char) Ch) == (char) ChFinal)
|
|
{
|
|
for (i = Length - 2;
|
|
i >= 0;
|
|
i--)
|
|
{
|
|
if (fseek (FileSrc,-2L,SEEK_CUR))
|
|
return false;
|
|
if ((Ch = Str_ReadCharAndSkipCommentsBackward (FileSrc,SkipHTMLComments)) == EOF) // Set pointer to '>' if not comment, or to character before start of comment if comment
|
|
return false;
|
|
if (Str_ConvertToLowerLetter ((char) Ch) != Str_ConvertToLowerLetter (Str[i]))
|
|
break;
|
|
}
|
|
if (i<0) // Found!
|
|
{
|
|
fseek (FileSrc,-1L,SEEK_CUR); // Move to start of found string in FileSrc
|
|
return true;
|
|
}
|
|
else // Not found, continue on next character
|
|
fseek (FileSrc,CurPos,SEEK_SET);
|
|
}
|
|
if (fseek (FileSrc,-2L,SEEK_CUR)) // Move to previous character
|
|
return false;
|
|
}
|
|
|
|
return false; // Not reached
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Search a string in a file writing in FileTgt ******************/
|
|
/*****************************************************************************/
|
|
/*
|
|
Search in the file FileSrc the string Str without distinguish uppercase from
|
|
lowercase. Write in FileTgt what is read from the file FileSrc.
|
|
*/
|
|
|
|
bool Str_WriteUntilStrFoundInFileIncludingStr (FILE *FileTgt,FILE *FileSrc,const char *Str,Str_SkipHTMLComments_t SkipHTMLComments)
|
|
{
|
|
int i;
|
|
int StrLength = strlen (Str);
|
|
int Ch;
|
|
long CurPos1,CurPos2;
|
|
|
|
for (;;)
|
|
{
|
|
/* Skip possible comments */
|
|
if ((Ch = Str_ReadCharAndSkipCommentsWriting (FileSrc,FileTgt,SkipHTMLComments)) == EOF) // Set pointer to '<' if not comment, or to first character after comment if comment
|
|
return false;
|
|
CurPos1 = ftell (FileSrc);
|
|
fputc (Ch,FileTgt);
|
|
|
|
if (Str_ConvertToLowerLetter ((char) Ch) == Str_ConvertToLowerLetter (Str[0])) // Found first character
|
|
{
|
|
for (i = 1;
|
|
i < StrLength;
|
|
i++)
|
|
{
|
|
/* Skip possible comments */
|
|
if ((Ch = Str_ReadCharAndSkipComments (FileSrc,SkipHTMLComments)) == EOF) // Set pointer to '<' if not comment, or to first character after comment if comment
|
|
return false;
|
|
if (Str_ConvertToLowerLetter ((char) Ch) != Str_ConvertToLowerLetter (Str[i]))
|
|
break;
|
|
}
|
|
if (i == StrLength) // Found!
|
|
{
|
|
CurPos2 = ftell (FileSrc);
|
|
fseek (FileSrc,CurPos1,SEEK_SET);
|
|
while (ftell (FileSrc) < CurPos2)
|
|
{
|
|
Ch = fgetc (FileSrc);
|
|
fputc (Ch,FileTgt);
|
|
}
|
|
return true;
|
|
}
|
|
else // Not found, continue on next character
|
|
fseek (FileSrc,CurPos1,SEEK_SET);
|
|
}
|
|
}
|
|
|
|
return false; // Not reached
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Skip HTML comments in FileSrc ***********************/
|
|
/*****************************************************************************/
|
|
// Set pointer to '<' if not comment, or to first character after comment if comment
|
|
// Returns last char read after comments
|
|
|
|
static int Str_ReadCharAndSkipComments (FILE *FileSrc,Str_SkipHTMLComments_t SkipHTMLComments)
|
|
{
|
|
int Ch;
|
|
char StrAux[1 + 1]; /* To check "!--" string */
|
|
|
|
Ch = fgetc (FileSrc);
|
|
|
|
if (SkipHTMLComments)
|
|
while (Ch == (int) '<')
|
|
{
|
|
/***** Check if "<!--" *****/
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"!"))
|
|
{
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Not a comment. Return to start of directive
|
|
return fgetc (FileSrc);
|
|
}
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"-"))
|
|
{
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Not a comment. Return to start of directive
|
|
return fgetc (FileSrc);
|
|
}
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"-"))
|
|
{
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Not a comment. Return to start of directive
|
|
return fgetc (FileSrc);
|
|
}
|
|
|
|
/***** It's a comment ==> skip comment *****/
|
|
Str_FindStrInFile (FileSrc,"-->",Str_NO_SKIP_HTML_COMMENTS);
|
|
|
|
/***** Get next character *****/
|
|
Ch = fgetc (FileSrc);
|
|
}
|
|
|
|
return Ch;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Skip HTML comments in FileSrc writing into FileTgt ************/
|
|
/*****************************************************************************/
|
|
// Set pointer to '<' if not comment, or to first character after comment if comment
|
|
// Returns last char read after comments
|
|
|
|
static int Str_ReadCharAndSkipCommentsWriting (FILE *FileSrc,FILE *FileTgt,Str_SkipHTMLComments_t SkipHTMLComments)
|
|
{
|
|
int Ch;
|
|
char StrAux[1 + 1]; /* To check "!--" string */
|
|
|
|
Ch = fgetc (FileSrc);
|
|
|
|
if (SkipHTMLComments)
|
|
while (Ch == (int) '<')
|
|
{
|
|
/***** Check if "<!--" *****/
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"!"))
|
|
{
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Not a comment. Return to start of directive
|
|
return fgetc (FileSrc);
|
|
}
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"-"))
|
|
{
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Not a comment. Return to start of directive
|
|
return fgetc (FileSrc);
|
|
}
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"-"))
|
|
{
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Not a comment. Return to start of directive
|
|
return fgetc (FileSrc);
|
|
}
|
|
|
|
/***** It's a comment ==> skip comment *****/
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS); // Return to start of comment
|
|
Str_WriteUntilStrFoundInFileIncludingStr (FileTgt,FileSrc,"-->",Str_NO_SKIP_HTML_COMMENTS); // Skip comment in search, and write it to FileTgt
|
|
|
|
/***** Get next character *****/
|
|
Ch = fgetc (FileSrc);
|
|
}
|
|
|
|
return Ch;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************************* Skip a comment ******************************/
|
|
/*****************************************************************************/
|
|
// Set pointer to '>' if not comment, or to character previous to start of comment if comment
|
|
// Returns char preceeding comments, of EOF if start of file reached
|
|
|
|
static int Str_ReadCharAndSkipCommentsBackward (FILE *FileSrc,Str_SkipHTMLComments_t SkipHTMLComments)
|
|
{
|
|
int Ch;
|
|
char StrAux[3 + 1]; /* To check "--" string */
|
|
|
|
Ch = fgetc (FileSrc);
|
|
|
|
if (SkipHTMLComments)
|
|
while (Ch == (int) '>')
|
|
{
|
|
/* Check if "-->" */
|
|
// Now: "<!--example of comment-->..."
|
|
// ^
|
|
if (fseek (FileSrc,-3L,SEEK_CUR)) // Go to start of possible "-->" */
|
|
return EOF;
|
|
|
|
// Now: "<!--example of comment-->..."
|
|
// ^
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,3),"-->")) // Not a comment
|
|
return '>';
|
|
|
|
/***** It's a comment end *****/
|
|
Str_FindStrInFileBack (FileSrc,"<!--",Str_NO_SKIP_HTML_COMMENTS); // Skip comment backwards
|
|
if (fseek (FileSrc,-1L,SEEK_CUR)) // Return to character previous to '<'
|
|
return EOF;
|
|
|
|
/***** Get character previous to '<' *****/
|
|
Ch = fgetc (FileSrc);
|
|
}
|
|
|
|
return Ch;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****** Scan next string in file FileSrc until find </td> *******/
|
|
/****** ( skipping comments <!--...--> and directives <...> ) *******/
|
|
/****** and write string into Str (MaxLength characters as much) *******/
|
|
/*****************************************************************************/
|
|
|
|
char *Str_GetCellFromHTMLTableSkipComments (FILE *FileSrc,char *Str,int MaxLength)
|
|
{
|
|
long CurPos;
|
|
long PosNextTR;
|
|
long PosTD;
|
|
int i = 0;
|
|
int Ch;
|
|
bool EndCellFound = false;
|
|
bool DirectiveFound;
|
|
bool SpaceFound;
|
|
char StrAux[1 + 1]; // To find next "/td>" or "nbsp;"
|
|
|
|
Str[0] = '\0';
|
|
|
|
/***** Find next <td ...> inside current row (before next <tr) *****/
|
|
CurPos = ftell (FileSrc);
|
|
|
|
Str_FindStrInFile (FileSrc,"<tr",Str_NO_SKIP_HTML_COMMENTS);
|
|
PosNextTR = ftell (FileSrc) - 3;
|
|
|
|
fseek (FileSrc,CurPos,SEEK_SET);
|
|
Str_FindStrInFile (FileSrc,"<td",Str_NO_SKIP_HTML_COMMENTS);
|
|
PosTD = ftell (FileSrc);
|
|
|
|
if (PosTD > PosNextTR)
|
|
{
|
|
// <td not found in this row ==> leave current position unchanged
|
|
fseek (FileSrc,CurPos,SEEK_SET);
|
|
return Str;
|
|
}
|
|
|
|
Str_FindStrInFile (FileSrc,">",Str_NO_SKIP_HTML_COMMENTS);
|
|
|
|
for (EndCellFound = false;
|
|
!EndCellFound;
|
|
)
|
|
{
|
|
if ((Ch = Str_ReadCharAndSkipComments (FileSrc,Str_SKIP_HTML_COMMENTS)) == EOF) // Set pointer to '<' if not comment, or to first character after comment if comment
|
|
break;
|
|
|
|
/***** Skip directives except </td> *****/
|
|
DirectiveFound = (Ch == (int) '<');
|
|
|
|
if (DirectiveFound) // Start of directive, not a comment
|
|
{
|
|
/* Check if it's </td> */
|
|
if (!strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"/")) // It's </
|
|
if (!strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"t")) // It's </t
|
|
if (!strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"d")) // It's </td
|
|
if (!strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),">")) // It's </td>
|
|
EndCellFound = true; // </td> found
|
|
|
|
if (!EndCellFound)
|
|
{
|
|
/* Skip directive */
|
|
Str_FindStrInFileBack (FileSrc,"<",Str_NO_SKIP_HTML_COMMENTS);
|
|
Str_FindStrInFile (FileSrc,">",Str_NO_SKIP_HTML_COMMENTS);
|
|
}
|
|
}
|
|
|
|
if (!EndCellFound)
|
|
{
|
|
if (DirectiveFound)
|
|
Ch = (int) ' '; // Replace directive for ' ' (separator)
|
|
else
|
|
{
|
|
/***** Check for space or *****/
|
|
SpaceFound = false;
|
|
|
|
if (Ch == (int) '&')
|
|
{
|
|
/* Check for (case insensitive) */
|
|
if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"n"))
|
|
{ // It's not &n
|
|
Str_FindStrInFileBack (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Back until &
|
|
Str_FindStrInFile (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Skip &
|
|
}
|
|
else if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"b"))
|
|
{ // It's not &nb
|
|
Str_FindStrInFileBack (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Back until &
|
|
Str_FindStrInFile (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Skip &
|
|
}
|
|
else if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"s"))
|
|
{ // It's not &nbs
|
|
Str_FindStrInFileBack (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Back until &
|
|
Str_FindStrInFile (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Skip &
|
|
}
|
|
else if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),"p"))
|
|
{ // It's not  
|
|
Str_FindStrInFileBack (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Back until &
|
|
Str_FindStrInFile (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Skip &
|
|
}
|
|
else if (strcasecmp (Str_GetNextStrFromFileConvertingToLower (FileSrc,StrAux,1),";"))
|
|
{ // It's not
|
|
Str_FindStrInFileBack (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Back until &
|
|
Str_FindStrInFile (FileSrc,"&",Str_NO_SKIP_HTML_COMMENTS); // Skip &
|
|
}
|
|
else // It's
|
|
SpaceFound = true;
|
|
}
|
|
|
|
/***** Skip spaces *****/
|
|
if (isspace (Ch) ||
|
|
Ch == 0xA0) // Unicode translation for
|
|
SpaceFound = true;
|
|
|
|
if (SpaceFound)
|
|
Ch = (int) ' '; // Replace any kind of space for ' ' (separator)
|
|
}
|
|
|
|
if (i < MaxLength)
|
|
Str[i++] = (char) Ch;
|
|
}
|
|
}
|
|
Str[i] = '\0';
|
|
return Str;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Read the next N characters in a string ******************/
|
|
/*****************************************************************************/
|
|
/*
|
|
Read from the file FileSrc the next N characters converting them
|
|
to lowercase and store them in Str. Return a pointer to the string.
|
|
*/
|
|
|
|
char *Str_GetNextStrFromFileConvertingToLower (FILE *FileSrc,char *Str, int N)
|
|
{
|
|
int i,Ch;
|
|
|
|
for (i = 0;
|
|
i < N;
|
|
i++)
|
|
{
|
|
if ((Ch = fgetc (FileSrc)) == EOF)
|
|
break;
|
|
Str[i] = Str_ConvertToLowerLetter ((char) Ch);
|
|
}
|
|
Str[i] = '\0';
|
|
return Str;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********** Get from StrSrc into StrDst the next string until space **********/
|
|
/*****************************************************************************/
|
|
// Modifies *StrSrc
|
|
|
|
void Str_GetNextStringUntilSpace (const char **StrSrc,char *StrDst,size_t MaxLength)
|
|
{
|
|
size_t i = 0;
|
|
int Ch;
|
|
|
|
/***** Skip leading spaces *****/
|
|
do
|
|
{
|
|
if ((Ch = (int) **StrSrc) != 0)
|
|
(*StrSrc)++;
|
|
}
|
|
while (isspace (Ch) ||
|
|
Ch == 0xA0); // Unicode translation for
|
|
|
|
/***** Copy string while non-space characters *****/
|
|
while (Ch &&
|
|
!(isspace (Ch) ||
|
|
Ch == 0xA0)) // Unicode translation for
|
|
{
|
|
if (i < MaxLength)
|
|
StrDst[i++] = (char) Ch;
|
|
if ((Ch = (int) **StrSrc) != 0)
|
|
(*StrSrc)++;
|
|
}
|
|
StrDst[i] = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******* Get from StrSrc into StrDst the next string until separator *********/
|
|
/*****************************************************************************/
|
|
// Modifies *StrSrc
|
|
|
|
void Str_GetNextStringUntilSeparator (const char **StrSrc,char *StrDst,size_t MaxLength)
|
|
{
|
|
size_t i = 0;
|
|
int Ch;
|
|
|
|
/***** Skip separators *****/
|
|
do
|
|
{
|
|
if ((Ch = (int) **StrSrc) != 0)
|
|
(*StrSrc)++;
|
|
}
|
|
while (isspace (Ch) ||
|
|
Ch == 0xA0 || // Unicode translation for
|
|
Ch == (int) ',' ||
|
|
Ch == (int) ';');
|
|
|
|
/***** Copy string while no separator found *****/
|
|
while (Ch &&
|
|
!(isspace (Ch) ||
|
|
Ch == 0xA0 || // Unicode translation for
|
|
Ch == (int) ',' ||
|
|
Ch == (int) ';'))
|
|
{
|
|
if (i < MaxLength)
|
|
StrDst[i++] = (char) Ch;
|
|
if ((Ch = (int) **StrSrc) != 0)
|
|
(*StrSrc)++;
|
|
}
|
|
StrDst[i] = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********** Get from StrSrc into StrDst the next string until comma **********/
|
|
/*****************************************************************************/
|
|
// Modifies *StrSrc
|
|
// Leading spaces are not copied
|
|
// Trailing spaces are removed at end
|
|
|
|
void Str_GetNextStringUntilComma (const char **StrSrc,char *StrDst,size_t MaxLength)
|
|
{
|
|
int Ch;
|
|
char *Ptr;
|
|
size_t i = 0;
|
|
|
|
/***** Skip leading spaces and ',' *****/
|
|
Ch = (int) **StrSrc;
|
|
while (isspace (Ch) ||
|
|
Ch == 0xA0 || // Unicode translation for
|
|
Ch == (int) ',')
|
|
{
|
|
(*StrSrc)++;
|
|
Ch = (int) **StrSrc;
|
|
}
|
|
|
|
/***** Copy string until ',' or end *****/
|
|
Ptr = StrDst;
|
|
while (Ch && Ch != (int) ',')
|
|
{
|
|
if (i < MaxLength)
|
|
{
|
|
*Ptr++ = (char) Ch;
|
|
i++;
|
|
}
|
|
(*StrSrc)++;
|
|
Ch = (int) **StrSrc;
|
|
}
|
|
|
|
/***** Remove trailing spaces *****/
|
|
for (Ptr--;
|
|
Ptr >= *StrSrc;
|
|
Ptr--)
|
|
if (!(isspace ((int) *Ptr) ||
|
|
*Ptr == '\xA0')) // Unicode translation for
|
|
break;
|
|
*(Ptr + 1) = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Replace several spaces of a string for one ****************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_ReplaceSeveralSpacesForOne (char *Str)
|
|
{
|
|
char *PtrSrc, *PtrDst;
|
|
bool PreviousWasSpace = false;
|
|
|
|
/***** Do the replacing *****/
|
|
for (PtrDst = PtrSrc = Str;
|
|
*PtrSrc;)
|
|
if (isspace ((int) *PtrSrc) ||
|
|
*PtrSrc == '\xA0') // Unicode translation for
|
|
{
|
|
if (!PreviousWasSpace)
|
|
*PtrDst++ = ' ';
|
|
PreviousWasSpace = true;
|
|
PtrSrc++;
|
|
}
|
|
else
|
|
{
|
|
PreviousWasSpace = false;
|
|
*PtrDst++ = *PtrSrc++;
|
|
}
|
|
*PtrDst = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Copy a string to another changing ' ' to '%20' ****************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_CopyStrChangingSpaces (const char *StringWithSpaces,char *StringWithoutSpaces,unsigned MaxLength)
|
|
{
|
|
const char *PtrSrc;
|
|
char *PtrDst;
|
|
unsigned Length = 0;
|
|
|
|
for (PtrSrc = StringWithSpaces, PtrDst = StringWithoutSpaces;
|
|
*PtrSrc && Length <= MaxLength;
|
|
PtrSrc++)
|
|
if (*PtrSrc == ' ')
|
|
{
|
|
Length += 3;
|
|
if (Length <= MaxLength)
|
|
{
|
|
*PtrDst++ = '%';
|
|
*PtrDst++ = '2';
|
|
*PtrDst++ = '0';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Length++;
|
|
if (Length <= MaxLength)
|
|
*PtrDst++ = *PtrSrc;
|
|
}
|
|
*PtrDst = '\0';
|
|
|
|
if (Length > MaxLength)
|
|
Lay_ShowErrorAndExit ("Path is too long.");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Convert string with a code (of group type, group, degree, etc.) to long **/
|
|
/*****************************************************************************/
|
|
// Return -1L if code not found in Str
|
|
|
|
long Str_ConvertStrCodToLongCod (const char *Str)
|
|
{
|
|
long Code;
|
|
|
|
if (!Str)
|
|
return -1L;
|
|
|
|
if (Str[0] == '\0')
|
|
return -1L;
|
|
|
|
if (sscanf (Str,"%ld",&Code) != 1)
|
|
return -1L;
|
|
|
|
return Code;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/**** Compute length of root (all except extension) of the name of a file ****/
|
|
/*****************************************************************************/
|
|
|
|
size_t Str_GetLengthRootFileName (const char *FileName)
|
|
{
|
|
char *PtrToDot = strrchr (FileName,(int) '.');
|
|
size_t LengthFileName = strlen (FileName);
|
|
|
|
if (PtrToDot)
|
|
return LengthFileName - strlen (PtrToDot);
|
|
else
|
|
return LengthFileName;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Get the name of a file from a complete path ******************/
|
|
/*****************************************************************************/
|
|
// Split a full path in path (without ending '/' ) and a file name
|
|
|
|
void Str_SplitFullPathIntoPathAndFileName (const char FullPath[PATH_MAX + 1],
|
|
char PathWithoutFileName[PATH_MAX + 1],
|
|
char FileName[NAME_MAX + 1])
|
|
{
|
|
const char *PtrFileName;
|
|
size_t LengthUntilFileName;
|
|
|
|
/***** Find the start of filename *****/
|
|
if ((PtrFileName = strrchr (FullPath,(int) '/')) != NULL)
|
|
PtrFileName++;
|
|
else if ((PtrFileName = strrchr (FullPath,(int) '\\')) != NULL)
|
|
PtrFileName++;
|
|
else
|
|
PtrFileName = FullPath;
|
|
|
|
/***** Get PathWithoutFileName *****/
|
|
LengthUntilFileName = (size_t) (PtrFileName - FullPath); // Last slash included
|
|
if (LengthUntilFileName > 1)
|
|
{
|
|
Str_Copy (PathWithoutFileName,FullPath,
|
|
PATH_MAX);
|
|
PathWithoutFileName[LengthUntilFileName - 1] = '\0'; // Do not copy ending slash
|
|
}
|
|
else
|
|
PathWithoutFileName[0] = '\0';
|
|
|
|
/***** Get FileName *****/
|
|
Str_Copy (FileName,PtrFileName,
|
|
NAME_MAX);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Check if the extension of a file is .Extension ***************/
|
|
/*****************************************************************************/
|
|
// Return true if FileName ends by .Extension
|
|
// Else return false
|
|
|
|
bool Str_FileIs (const char *FileName,const char *Extension)
|
|
{
|
|
int i;
|
|
int j;
|
|
size_t LengthExtension = strlen (Extension);
|
|
|
|
/***** Check length of extension. Extension valid are, for example "zip", "html", "mhtml" *****/
|
|
if (LengthExtension < Fil_MIN_BYTES_FILE_EXTENSION ||
|
|
LengthExtension > Fil_MAX_BYTES_FILE_EXTENSION)
|
|
return false;
|
|
|
|
/***** Check the extension *****/
|
|
for (i = strlen (FileName) - 1, j = LengthExtension - 1;
|
|
i > 0 && j >= 0;
|
|
i--, j--)
|
|
if (Str_ConvertToLowerLetter (FileName[i]) != Str_ConvertToLowerLetter (Extension[j]))
|
|
return false;
|
|
|
|
if (j >= 0) /* If all the characters of the extension have not been checked
|
|
due to the name of the file is too short */
|
|
return false;
|
|
|
|
/***** Check the dot before the extension *****/
|
|
return (FileName[i] == '.');
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/**************** Check if the extension of a file is .html ******************/
|
|
/*****************************************************************************/
|
|
// Return true if FileName ends by .htm or .html
|
|
// Else return false
|
|
|
|
bool Str_FileIsHTML (const char *FileName)
|
|
{
|
|
if (Str_FileIs (FileName,"htm"))
|
|
return true;
|
|
return Str_FileIs (FileName,"html");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************* Check if Path1 starts by Path2 ************************/
|
|
/*****************************************************************************/
|
|
|
|
bool Str_Path1BeginsByPath2 (const char *Path1,const char *Path2)
|
|
{
|
|
/* The string Path1 must start by the complete string Path2 */
|
|
while (*Path2)
|
|
if (*Path2++ != *Path1++)
|
|
return false;
|
|
|
|
/* The string Path1 starts by the complete string Path2 */
|
|
/* Check that the next character of Path1 is '\0' or '/' */
|
|
return (bool) (*Path1 == '\0' || *Path1 == '/');
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/** Skip spaces in a file seeking it before of reading the first non-blank ***/
|
|
/*****************************************************************************/
|
|
|
|
void Str_SkipSpacesInFile (FILE *FileSrc)
|
|
{
|
|
int Ch;
|
|
|
|
while ((Ch = fgetc (FileSrc)) != EOF)
|
|
if (!(isspace (Ch) ||
|
|
Ch == 0xA0)) // Unicode translation for
|
|
{
|
|
fseek (FileSrc,-1L,SEEK_CUR);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Write a string to a file changing *************************/
|
|
/***************** <br> or <br /> for return *************************/
|
|
/***************** and for space *************************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_FilePrintStrChangingBRToRetAndNBSPToSpace (FILE *FileTgt,const char *Str)
|
|
{
|
|
while (*Str)
|
|
{
|
|
/* Is ? */
|
|
if (*Str == '&')
|
|
{
|
|
if (*(Str + 1) == 'N' || *(Str + 1) == 'n')
|
|
if (*(Str + 2) == 'B' || *(Str + 2) == 'b')
|
|
if (*(Str + 3) == 'S' || *(Str + 3) == 's')
|
|
if (*(Str + 4) == 'P' || *(Str + 4) == 'p')
|
|
if (*(Str + 5) == ';')
|
|
{
|
|
fputc ((int) ' ',FileTgt);
|
|
Str += 6;
|
|
continue;
|
|
}
|
|
}
|
|
/* Is <br> or <br />? */
|
|
else if (*Str == '<')
|
|
{
|
|
if (*(Str + 1) == 'B' || *(Str + 1) == 'b')
|
|
if (*(Str + 2) == 'R' || *(Str + 2) == 'r')
|
|
{
|
|
if (*(Str + 3) == '>')
|
|
{
|
|
fputc ((int) '\n',FileTgt);
|
|
Str += 4;
|
|
continue;
|
|
}
|
|
else if (*(Str + 3) == ' ')
|
|
{
|
|
if (*(Str + 4) == '/')
|
|
if (*(Str + 5) == '>')
|
|
{
|
|
fputc ((int) '\n',FileTgt);
|
|
Str += 6;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fputc ((int) *Str,FileTgt);
|
|
Str++;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Search a string in a file and/or in a string ****************/
|
|
/*****************************************************************************/
|
|
/*
|
|
Search in the file FileSrc the string StrDelimit.
|
|
Write in the file FileTgt and/or StrDst the characters read from FileSrc, not including StrDelimit!.
|
|
StrDst can be NULL if you don't want to use them.
|
|
If StrDelimit is found, return 1.
|
|
If what is read exceed MaxLength, abort and return 0.
|
|
If StrDelimit is not found, return -1.
|
|
*/
|
|
|
|
#define Str_MAX_BYTES_BOUNDARY_STR 100
|
|
|
|
int Str_ReadFileUntilBoundaryStr (FILE *FileSrc,char *StrDst,
|
|
const char *BoundaryStr,
|
|
unsigned LengthBoundaryStr,
|
|
unsigned long long MaxLength)
|
|
{
|
|
unsigned NumBytesIdentical; // Number of characters identical in each iteration of the loop
|
|
unsigned NumBytesReadButNotDiscarded; // Number of characters read from the source file...
|
|
// ...and not fully discarded in search
|
|
int Buffer[Str_MAX_BYTES_BOUNDARY_STR + 1];
|
|
unsigned StartIndex;
|
|
unsigned i;
|
|
char *Ptr; // Pointer used to go through StrDst writing characters
|
|
unsigned long long LengthDst;
|
|
|
|
/***** Checkings on boundary string *****/
|
|
if (!LengthBoundaryStr)
|
|
{
|
|
if (StrDst != NULL)
|
|
*StrDst = '\0';
|
|
return 1;
|
|
}
|
|
if (LengthBoundaryStr > Str_MAX_BYTES_BOUNDARY_STR)
|
|
Lay_ShowErrorAndExit ("Delimiter string too large.");
|
|
Ptr = StrDst;
|
|
|
|
StartIndex = 0;
|
|
NumBytesReadButNotDiscarded = 0;
|
|
LengthDst = 0;
|
|
|
|
for (;;)
|
|
{
|
|
if (!NumBytesReadButNotDiscarded)
|
|
{ // Read next character
|
|
Buffer[StartIndex] = fgetc (FileSrc);
|
|
if (feof (FileSrc))
|
|
{
|
|
if (StrDst != NULL)
|
|
*Ptr = '\0';
|
|
return -1;
|
|
}
|
|
NumBytesReadButNotDiscarded++;
|
|
}
|
|
|
|
if (Buffer[StartIndex] == (int) BoundaryStr[0]) // First character identical
|
|
{
|
|
for (NumBytesIdentical = 1, i = (StartIndex + 1) % LengthBoundaryStr;
|
|
NumBytesIdentical < LengthBoundaryStr;
|
|
NumBytesIdentical++, i = (i + 1) % LengthBoundaryStr)
|
|
{
|
|
if (NumBytesReadButNotDiscarded == NumBytesIdentical) // Last character is identical
|
|
{
|
|
Buffer[i] = fgetc (FileSrc); // Read next character
|
|
if (feof (FileSrc))
|
|
{
|
|
if (StrDst != NULL)
|
|
*Ptr = '\0';
|
|
return -1;
|
|
}
|
|
NumBytesReadButNotDiscarded++;
|
|
}
|
|
if (Buffer[i] != (int) BoundaryStr[NumBytesIdentical]) // Next character is different
|
|
break;
|
|
}
|
|
if (NumBytesIdentical == LengthBoundaryStr) // Boundary found
|
|
{
|
|
if (StrDst != NULL)
|
|
*Ptr = '\0';
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (LengthDst == MaxLength)
|
|
{
|
|
if (StrDst != NULL)
|
|
*Ptr = '\0';
|
|
return 0;
|
|
}
|
|
|
|
if (StrDst != NULL)
|
|
*Ptr++ = (char) Buffer[StartIndex];
|
|
|
|
StartIndex = (StartIndex + 1) % LengthBoundaryStr;
|
|
NumBytesReadButNotDiscarded--;
|
|
LengthDst++;
|
|
}
|
|
|
|
return 0; // Not reached
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****** Convert invalid characters in a file name to valid characters ********/
|
|
/*****************************************************************************/
|
|
// Return true if the name of the file o folder is valid
|
|
// If the name is not valid, Gbl.Alert.Txt will contain feedback text
|
|
// File names with heading and trailing spaces are allowed
|
|
|
|
bool Str_ConvertFilFolLnkNameToValid (char *FileName)
|
|
{
|
|
extern const char *Txt_UPLOAD_FILE_X_invalid_name_NO_HTML;
|
|
extern const char *Txt_UPLOAD_FILE_X_invalid_name;
|
|
extern const char *Txt_UPLOAD_FILE_Invalid_name_NO_HTML;
|
|
extern const char *Txt_UPLOAD_FILE_Invalid_name;
|
|
char *Ptr;
|
|
unsigned NumAlfanum = 0;
|
|
unsigned NumSpaces = 0;
|
|
unsigned NumPoints = 0;
|
|
bool FileNameIsOK = false;
|
|
|
|
Ptr = FileName;
|
|
if (*Ptr) // FileName is not empty
|
|
{
|
|
for (;
|
|
*Ptr;
|
|
Ptr++)
|
|
{
|
|
if (*Ptr == ' ')
|
|
{
|
|
NumPoints = 0;
|
|
if (++NumSpaces > 1)
|
|
{
|
|
*Ptr = '_';
|
|
NumAlfanum++;
|
|
NumSpaces = NumPoints = 0;
|
|
}
|
|
}
|
|
else if (*Ptr == '.')
|
|
{
|
|
if (++NumPoints > 1) // Don't allow ".."
|
|
{
|
|
*Ptr = '_';
|
|
NumAlfanum++;
|
|
NumSpaces = NumPoints = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (*Ptr)
|
|
{
|
|
case 'á': *Ptr = 'a'; break;
|
|
case 'é': *Ptr = 'e'; break;
|
|
case 'í': *Ptr = 'i'; break;
|
|
case 'ó': *Ptr = 'o'; break;
|
|
case 'ú': *Ptr = 'u'; break;
|
|
case 'ñ': *Ptr = 'n'; break;
|
|
case 'ä': *Ptr = 'a'; break;
|
|
case 'ë': *Ptr = 'e'; break;
|
|
case 'ï': *Ptr = 'i'; break;
|
|
case 'ö': *Ptr = 'o'; break;
|
|
case 'ü': *Ptr = 'u'; break;
|
|
case 'ç': *Ptr = 'c'; break;
|
|
|
|
case 'Á': *Ptr = 'A'; break;
|
|
case 'É': *Ptr = 'E'; break;
|
|
case 'Í': *Ptr = 'I'; break;
|
|
case 'Ó': *Ptr = 'O'; break;
|
|
case 'Ú': *Ptr = 'U'; break;
|
|
case 'Ñ': *Ptr = 'N'; break;
|
|
case 'Ä': *Ptr = 'A'; break;
|
|
case 'Ë': *Ptr = 'E'; break;
|
|
case 'Ï': *Ptr = 'I'; break;
|
|
case 'Ö': *Ptr = 'O'; break;
|
|
case 'Ü': *Ptr = 'U'; break;
|
|
case 'Ç': *Ptr = 'C'; break;
|
|
}
|
|
if ((*Ptr >= 'a' && *Ptr <= 'z') ||
|
|
(*Ptr >= 'A' && *Ptr <= 'Z') ||
|
|
(*Ptr >= '0' && *Ptr <= '9') ||
|
|
*Ptr == '_' ||
|
|
*Ptr == '-')
|
|
{
|
|
NumAlfanum++;
|
|
NumSpaces = NumPoints = 0;
|
|
}
|
|
else
|
|
{
|
|
*Ptr = '_';
|
|
NumAlfanum++;
|
|
NumSpaces = NumPoints = 0;
|
|
}
|
|
}
|
|
}
|
|
if (NumAlfanum)
|
|
FileNameIsOK = true;
|
|
else
|
|
snprintf (Gbl.Alert.Txt,Ale_MAX_BYTES_ALERT,
|
|
Gbl.FileBrowser.UploadingWithDropzone ? Txt_UPLOAD_FILE_X_invalid_name_NO_HTML :
|
|
Txt_UPLOAD_FILE_X_invalid_name,
|
|
FileName);
|
|
}
|
|
else // FileName is empty
|
|
sprintf (Gbl.Alert.Txt,"%s",
|
|
Gbl.FileBrowser.UploadingWithDropzone ? Txt_UPLOAD_FILE_Invalid_name_NO_HTML :
|
|
Txt_UPLOAD_FILE_Invalid_name);
|
|
|
|
return FileNameIsOK;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************ Convert a string to a valid name of file or folder *************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_ConvertToValidFileName (char *Str)
|
|
{
|
|
char *Ptr;
|
|
|
|
for (Ptr = Str;
|
|
*Ptr;
|
|
Ptr++)
|
|
{
|
|
if ((*Ptr >= 'a' && *Ptr <= 'z') ||
|
|
(*Ptr >= 'A' && *Ptr <= 'Z') ||
|
|
(*Ptr >= '0' && *Ptr <= '9') ||
|
|
*Ptr == '_' ||
|
|
*Ptr == '-')
|
|
continue;
|
|
if (isspace ((int) *Ptr) ||
|
|
*Ptr == '\xA0')
|
|
*Ptr = '_';
|
|
else
|
|
switch (*Ptr)
|
|
{
|
|
case 'á': case 'à': case 'ä': case 'â': *Ptr = 'a'; break;
|
|
case 'é': case 'è': case 'ë': case 'ê': *Ptr = 'e'; break;
|
|
case 'í': case 'ì': case 'ï': case 'î': *Ptr = 'i'; break;
|
|
case 'ó': case 'ò': case 'ö': case 'ô': *Ptr = 'o'; break;
|
|
case 'ú': case 'ù': case 'ü': case 'û': *Ptr = 'u'; break;
|
|
case 'ñ': *Ptr = 'n'; break;
|
|
case 'ç': *Ptr = 'c'; break;
|
|
|
|
case 'Á': case 'À': case 'Ä': case 'Â': *Ptr = 'A'; break;
|
|
case 'É': case 'È': case 'Ë': case 'Ê': *Ptr = 'E'; break;
|
|
case 'Í': case 'Ì': case 'Ï': case 'Î': *Ptr = 'I'; break;
|
|
case 'Ó': case 'Ò': case 'Ö': case 'Ô': *Ptr = 'O'; break;
|
|
case 'Ú': case 'Ù': case 'Ü': case 'Û': *Ptr = 'U'; break;
|
|
case 'Ñ': *Ptr = 'N'; break;
|
|
case 'Ç': *Ptr = 'C'; break;
|
|
|
|
default: *Ptr = '-'; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Create a random alphanumeric string *********************/
|
|
/*****************************************************************************/
|
|
|
|
#define NUM_ALPHANUM_CHARS (10 + 26 + 26)
|
|
|
|
void Str_CreateRandomAlphanumStr (char *Str,size_t Length)
|
|
{
|
|
static const char CharTable[NUM_ALPHANUM_CHARS] =
|
|
{'0','1','2','3','4','5','6','7','8','9',
|
|
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
|
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
|
|
};
|
|
size_t i;
|
|
|
|
/***** Set random chars in string *****/
|
|
for (i = 0;
|
|
i <Length;
|
|
i++)
|
|
Str[i] = CharTable[(unsigned) (((float) rand () * (float) (NUM_ALPHANUM_CHARS-1)) / (float) RAND_MAX + 0.5)];
|
|
Str[Length] = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************************** Safe string copy *****************************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_Copy (char *Dst,const char *Src,size_t DstSize)
|
|
{
|
|
size_t LengthSrc = strlen (Src);
|
|
|
|
/***** Check if buffer has enough space for source *****/
|
|
if (LengthSrc > DstSize)
|
|
{
|
|
sprintf (Gbl.Alert.Txt,"Trying to copy %lu chars into a %lu-chars buffer.",
|
|
LengthSrc,DstSize);
|
|
Lay_ShowErrorAndExit (Gbl.Alert.Txt);
|
|
}
|
|
|
|
/***** Copy source into destination *****/
|
|
strcpy (Dst,Src);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************** Safe string concatenation ************************/
|
|
/*****************************************************************************/
|
|
|
|
void Str_Concat (char *Dst,const char *Src,size_t DstSize)
|
|
{
|
|
size_t LengthDst;
|
|
size_t LengthSrc;
|
|
size_t FreeSpace;
|
|
|
|
/***** Check if buffer has already overflowed *****/
|
|
LengthDst = strlen (Dst);
|
|
if (LengthDst > DstSize)
|
|
{
|
|
sprintf (Gbl.Alert.Txt,"%lu-chars buffer has %lu chars!",
|
|
DstSize,LengthDst);
|
|
Lay_ShowErrorAndExit (Gbl.Alert.Txt);
|
|
}
|
|
|
|
/***** Check if buffer has enough space for source *****/
|
|
// DstSize >= LengthDst ==> FreeSpace >= 0
|
|
FreeSpace = DstSize - LengthDst;
|
|
LengthSrc = strlen (Src);
|
|
if (FreeSpace < LengthSrc)
|
|
{
|
|
sprintf (Gbl.Alert.Txt,"Trying to concatenate %lu chars to a %lu-chars buffer with free space for only %lu chars!",
|
|
LengthSrc,DstSize,FreeSpace);
|
|
Lay_ShowErrorAndExit (Gbl.Alert.Txt);
|
|
}
|
|
|
|
/***** Concatenate ******/
|
|
strcat (Dst,Src);
|
|
}
|