mirror of https://github.com/acanas/swad-core.git
Version 15.169.1
This commit is contained in:
parent
04f30ff800
commit
a4f4855de2
|
@ -138,13 +138,14 @@
|
||||||
/****************************** Public constants *****************************/
|
/****************************** Public constants *****************************/
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
#define Log_PLATFORM_VERSION "SWAD 15.169 (2016-03-31)"
|
#define Log_PLATFORM_VERSION "SWAD 15.169.1 (2016-03-31)"
|
||||||
#define CSS_FILE "swad15.165.5.css"
|
#define CSS_FILE "swad15.165.5.css"
|
||||||
#define JS_FILE "swad15.131.3.js"
|
#define JS_FILE "swad15.131.3.js"
|
||||||
|
|
||||||
// Number of lines (includes comments but not blank lines) has been got with the following command:
|
// Number of lines (includes comments but not blank lines) has been got with the following command:
|
||||||
// nl swad*.c swad*.h css/swad*.css py/swad*.py js/swad*.js soap/swad*.h sql/swad*.sql | tail -1
|
// nl swad*.c swad*.h css/swad*.css py/swad*.py js/swad*.js soap/swad*.h sql/swad*.sql | tail -1
|
||||||
/*
|
/*
|
||||||
|
Version 15.169.1: Mar 31, 2016 Code refactoring in list of parameters. (197298 lines)
|
||||||
Version 15.169: Mar 31, 2016 When content is data, all parameters are retrieved in a list. (197306 lines)
|
Version 15.169: Mar 31, 2016 When content is data, all parameters are retrieved in a list. (197306 lines)
|
||||||
Version 15.168.3: Mar 30, 2016 Code refactoring in list of parameters. (197124 lines)
|
Version 15.168.3: Mar 30, 2016 Code refactoring in list of parameters. (197124 lines)
|
||||||
Version 15.168.2: Mar 30, 2016 Code refactoring in list of parameters. (197128 lines)
|
Version 15.168.2: Mar 30, 2016 Code refactoring in list of parameters. (197128 lines)
|
||||||
|
|
|
@ -108,6 +108,7 @@ struct Globals
|
||||||
Act_CONTENT_NORM (if CONTENT_TYPE==text/plain) or
|
Act_CONTENT_NORM (if CONTENT_TYPE==text/plain) or
|
||||||
Act_CONTENT_DATA (if CONTENT_TYPE==multipart/form-data) */
|
Act_CONTENT_DATA (if CONTENT_TYPE==multipart/form-data) */
|
||||||
char DelimiterString[1000];
|
char DelimiterString[1000];
|
||||||
|
size_t LengthDelimiterString;
|
||||||
char DelimiterStringIncludingInitialRet[2+1000];
|
char DelimiterStringIncludingInitialRet[2+1000];
|
||||||
|
|
||||||
struct soap *soap; // gSOAP runtime environment
|
struct soap *soap; // gSOAP runtime environment
|
||||||
|
|
154
swad_parameter.c
154
swad_parameter.c
|
@ -62,6 +62,7 @@ extern struct Globals Gbl;
|
||||||
static void Par_CreateListOfParams (void);
|
static void Par_CreateListOfParams (void);
|
||||||
static void Par_CreateListOfParamsFromQueryString (void);
|
static void Par_CreateListOfParamsFromQueryString (void);
|
||||||
static void Par_CreateListOfParamsFromTmpFile (void);
|
static void Par_CreateListOfParamsFromTmpFile (void);
|
||||||
|
static bool Par_ReadTmpFileUntilDelimitStr (void);
|
||||||
static int Par_ReadTmpFileUntilQuote (void);
|
static int Par_ReadTmpFileUntilQuote (void);
|
||||||
static int Par_ReadTmpFileUntilReturn (void);
|
static int Par_ReadTmpFileUntilReturn (void);
|
||||||
|
|
||||||
|
@ -125,8 +126,15 @@ bool Par_GetQueryString (void)
|
||||||
"\r\n-----------------------------7d13ca2e948"
|
"\r\n-----------------------------7d13ca2e948"
|
||||||
I.e. 0x0D, 0x0A, '-', '-', and boundary.
|
I.e. 0x0D, 0x0A, '-', '-', and boundary.
|
||||||
*/
|
*/
|
||||||
sprintf (Gbl.DelimiterString,"--%s",strstr (getenv ("CONTENT_TYPE"),"boundary=") + strlen ("boundary="));
|
sprintf (Gbl.DelimiterString,"--%s",
|
||||||
|
strstr (getenv ("CONTENT_TYPE"),"boundary=") + strlen ("boundary="));
|
||||||
|
Gbl.LengthDelimiterString = strlen (Gbl.DelimiterString);
|
||||||
|
if (Gbl.LengthDelimiterString == 0 ||
|
||||||
|
Gbl.LengthDelimiterString > Par_MAX_LENGTH_STR_DELIMIT)
|
||||||
|
Lay_ShowErrorAndExit ("Wrong delimiter string.");
|
||||||
|
|
||||||
sprintf (Gbl.DelimiterStringIncludingInitialRet,"%c%c%s",0x0D,0x0A,Gbl.DelimiterString);
|
sprintf (Gbl.DelimiterStringIncludingInitialRet,"%c%c%s",0x0D,0x0A,Gbl.DelimiterString);
|
||||||
|
|
||||||
return Fil_ReadStdinIntoTmpFile ();
|
return Fil_ReadStdinIntoTmpFile ();
|
||||||
}
|
}
|
||||||
else if (!strncmp (ContentType,"text/xml",strlen ("text/xml")))
|
else if (!strncmp (ContentType,"text/xml",strlen ("text/xml")))
|
||||||
|
@ -151,11 +159,12 @@ bool Par_GetQueryString (void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Gbl.ContentReceivedByCGI == Act_CONTENT_NORM)
|
/***** Create list of parameters *****/
|
||||||
Par_CreateListOfParams ();
|
Par_CreateListOfParams ();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
/************************ Create list of parameters **************************/
|
/************************ Create list of parameters **************************/
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
@ -184,15 +193,20 @@ List --> |Name.Start | -> |Name.Start |
|
||||||
|
|
||||||
static void Par_CreateListOfParams (void)
|
static void Par_CreateListOfParams (void)
|
||||||
{
|
{
|
||||||
switch (Gbl.ContentReceivedByCGI)
|
/***** Initialize empty list of parameters *****/
|
||||||
{
|
Gbl.Params.List = NULL;
|
||||||
case Act_CONTENT_NORM:
|
|
||||||
Par_CreateListOfParamsFromQueryString ();
|
/***** Get list *****/
|
||||||
break;
|
if (Gbl.Params.ContentLength)
|
||||||
case Act_CONTENT_DATA:
|
switch (Gbl.ContentReceivedByCGI)
|
||||||
Par_CreateListOfParamsFromTmpFile ();
|
{
|
||||||
break;
|
case Act_CONTENT_NORM:
|
||||||
}
|
Par_CreateListOfParamsFromQueryString ();
|
||||||
|
break;
|
||||||
|
case Act_CONTENT_DATA:
|
||||||
|
Par_CreateListOfParamsFromTmpFile ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
@ -206,13 +220,8 @@ static void Par_CreateListOfParamsFromQueryString (void)
|
||||||
struct Param *NewParam;
|
struct Param *NewParam;
|
||||||
|
|
||||||
/***** Check if query string is empty *****/
|
/***** Check if query string is empty *****/
|
||||||
Gbl.Params.List = NULL;
|
if (!Gbl.Params.QueryString) return;
|
||||||
if (!Gbl.Params.ContentLength)
|
if (!Gbl.Params.QueryString[0]) return;
|
||||||
return;
|
|
||||||
if (Gbl.Params.QueryString == NULL)
|
|
||||||
return;
|
|
||||||
if (!Gbl.Params.QueryString[0])
|
|
||||||
return;
|
|
||||||
|
|
||||||
/***** Go over the query string
|
/***** Go over the query string
|
||||||
getting start positions and lengths of parameters *****/
|
getting start positions and lengths of parameters *****/
|
||||||
|
@ -262,41 +271,31 @@ static void Par_CreateListOfParamsFromQueryString (void)
|
||||||
// TODO: Rename Gbl.F.Tmp to Gbl.F.In (InFile, QueryFile)?
|
// TODO: Rename Gbl.F.Tmp to Gbl.F.In (InFile, QueryFile)?
|
||||||
|
|
||||||
#define Par_MAX_BYTES_STR_AUX 1024
|
#define Par_MAX_BYTES_STR_AUX 1024
|
||||||
|
#define Par_LENGTH_OF_STR_BEFORE_PARAM 38 // Length of "Content-Disposition: form-data; name=\""
|
||||||
|
#define Par_LENGTH_OF_STR_FILENAME 12 // Length of "; filename=\""
|
||||||
|
#define Par_LENGTH_OF_STR_CONTENT_TYPE 14 // Length of "Content-Type: "
|
||||||
|
|
||||||
static void Par_CreateListOfParamsFromTmpFile (void)
|
static void Par_CreateListOfParamsFromTmpFile (void)
|
||||||
{
|
{
|
||||||
static const char *StringBeforeParam = "Content-Disposition: form-data; name=\"";
|
static const char *StringBeforeParam = "Content-Disposition: form-data; name=\"";
|
||||||
#define Par_LENGTH_OF_STR_BEFORE_PARAM 38 // Length of "Content-Disposition: form-data; name=\""
|
|
||||||
static const char *StringFilename = "; filename=\"";
|
static const char *StringFilename = "; filename=\"";
|
||||||
#define Par_LENGTH_OF_STR_FILENAME 12 // Length of "; filename=\""
|
|
||||||
static const char *StringContentType = "Content-Type: ";
|
static const char *StringContentType = "Content-Type: ";
|
||||||
#define Par_LENGTH_OF_STR_CONTENT_TYPE 14 // Length of "Content-Type: "
|
unsigned long CurPos; // Current position in temporal file
|
||||||
|
|
||||||
unsigned long CurPos; // Current position in query string
|
|
||||||
struct Param *Param;
|
struct Param *Param;
|
||||||
struct Param *NewParam;
|
struct Param *NewParam;
|
||||||
int Result;
|
|
||||||
int Ch;
|
int Ch;
|
||||||
char StrAux[Par_MAX_BYTES_STR_AUX+1];
|
char StrAux[Par_MAX_BYTES_STR_AUX+1];
|
||||||
|
|
||||||
/***** Check if file is empty *****/
|
|
||||||
Gbl.Params.List = NULL;
|
|
||||||
if (!Gbl.Params.ContentLength)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/***** Go over the file
|
/***** Go over the file
|
||||||
getting start positions and lengths of parameters *****/
|
getting start positions and lengths of parameters *****/
|
||||||
Result = Str_SkipFileUntilDelimitStr (Gbl.F.Tmp,Gbl.DelimiterString);
|
if (Par_ReadTmpFileUntilDelimitStr ()) // Delimiter string found
|
||||||
if (Result == 1) // Delimiter string found
|
|
||||||
for (CurPos = 0;
|
for (CurPos = 0;
|
||||||
CurPos < Gbl.Params.ContentLength;
|
CurPos < Gbl.Params.ContentLength;
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
/***** Skip \r\n after delimiter string *****/
|
/***** Skip \r\n after delimiter string *****/
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0D) // '\r'
|
if (fgetc (Gbl.F.Tmp) != 0x0D) break; // '\r'
|
||||||
break;
|
if (fgetc (Gbl.F.Tmp) != 0x0A) break; // '\n'
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0A) // '\n'
|
|
||||||
break;
|
|
||||||
|
|
||||||
Str_GetNextStrFromFileConvertingToLower (Gbl.F.Tmp,StrAux,
|
Str_GetNextStrFromFileConvertingToLower (Gbl.F.Tmp,StrAux,
|
||||||
Par_LENGTH_OF_STR_BEFORE_PARAM);
|
Par_LENGTH_OF_STR_BEFORE_PARAM);
|
||||||
|
@ -323,8 +322,7 @@ static void Par_CreateListOfParamsFromTmpFile (void)
|
||||||
Param->Name.Length = CurPos - Param->Name.Start;
|
Param->Name.Length = CurPos - Param->Name.Start;
|
||||||
|
|
||||||
/* Check if last character read after parameter name is a quote */
|
/* Check if last character read after parameter name is a quote */
|
||||||
if (Ch != (int) '\"')
|
if (Ch != (int) '\"') break; // '\"'
|
||||||
break;
|
|
||||||
|
|
||||||
/* Get next char after parameter name */
|
/* Get next char after parameter name */
|
||||||
Ch = fgetc (Gbl.F.Tmp);
|
Ch = fgetc (Gbl.F.Tmp);
|
||||||
|
@ -344,14 +342,11 @@ static void Par_CreateListOfParamsFromTmpFile (void)
|
||||||
Param->Filename.Length = CurPos - Param->Filename.Start;
|
Param->Filename.Length = CurPos - Param->Filename.Start;
|
||||||
|
|
||||||
/* Check if last character read after filename is a quote */
|
/* Check if last character read after filename is a quote */
|
||||||
if (Ch != (int) '\"')
|
if (Ch != (int) '\"') break; // '\"'
|
||||||
break;
|
|
||||||
|
|
||||||
/* Skip \r\n */
|
/* Skip \r\n */
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0D) // '\r'
|
if (fgetc (Gbl.F.Tmp) != 0x0D) break; // '\r'
|
||||||
break;
|
if (fgetc (Gbl.F.Tmp) != 0x0A) break; // '\n'
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0A) // '\n'
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* Check if Content-Type is present */
|
/* Check if Content-Type is present */
|
||||||
Str_GetNextStrFromFileConvertingToLower (Gbl.F.Tmp,StrAux,
|
Str_GetNextStrFromFileConvertingToLower (Gbl.F.Tmp,StrAux,
|
||||||
|
@ -370,31 +365,76 @@ static void Par_CreateListOfParamsFromTmpFile (void)
|
||||||
|
|
||||||
/***** Now \r\n\r\n is expected just before parameter value or file content *****/
|
/***** Now \r\n\r\n is expected just before parameter value or file content *****/
|
||||||
/* Check if last character read is '\r' */
|
/* Check if last character read is '\r' */
|
||||||
if (Ch != 0x0D)
|
if (Ch != 0x0D) break; // '\r'
|
||||||
break;
|
|
||||||
|
|
||||||
/* Skip \n\r\n */
|
/* Skip \n\r\n */
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0A) // '\n'
|
if (fgetc (Gbl.F.Tmp) != 0x0A) break; // '\n'
|
||||||
break;
|
if (fgetc (Gbl.F.Tmp) != 0x0D) break; // '\r'
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0D) // '\r'
|
if (fgetc (Gbl.F.Tmp) != 0x0A) break; // '\n'
|
||||||
break;
|
|
||||||
if (fgetc (Gbl.F.Tmp) != 0x0A) // '\n'
|
|
||||||
break;
|
|
||||||
|
|
||||||
/***** Get parameter value or file content *****/
|
/***** Get parameter value or file content *****/
|
||||||
CurPos = (unsigned long) ftell (Gbl.F.Tmp);
|
CurPos = (unsigned long) ftell (Gbl.F.Tmp);
|
||||||
Result = Str_SkipFileUntilDelimitStr (Gbl.F.Tmp,Gbl.DelimiterString);
|
if (!Par_ReadTmpFileUntilDelimitStr ()) break; // Delimiter string not found
|
||||||
if (Result != 1) // Delimiter string not found
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Delimiter string found
|
// Delimiter string found
|
||||||
Param->Value.Start = CurPos;
|
Param->Value.Start = CurPos;
|
||||||
CurPos = (unsigned long) ftell (Gbl.F.Tmp);
|
CurPos = (unsigned long) ftell (Gbl.F.Tmp); // Just after delimiter string
|
||||||
Param->Value.Length = CurPos - Param->Filename.Start;
|
Param->Value.Length = CurPos - Gbl.LengthDelimiterString -
|
||||||
|
Param->Filename.Start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/******************** Read from file until quote '\"' ************************/
|
||||||
|
/*****************************************************************************/
|
||||||
|
// Return true if delimiter string is found.
|
||||||
|
// File is positioned just after the last character in delimiter string
|
||||||
|
|
||||||
|
static bool Par_ReadTmpFileUntilDelimitStr (void)
|
||||||
|
{
|
||||||
|
unsigned NumBytesIdentical; // Number of characters identical in each iteration of the loop
|
||||||
|
unsigned NumBytesReadButNoWritten = 0; // Number of characters read from the source file
|
||||||
|
// and not written in the destination file
|
||||||
|
int Buffer[Par_MAX_LENGTH_STR_DELIMIT+1];
|
||||||
|
unsigned StartIndex = 0;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (!NumBytesReadButNoWritten)
|
||||||
|
{ // Read next character
|
||||||
|
Buffer[StartIndex] = fgetc (Gbl.F.Tmp);
|
||||||
|
if (feof (Gbl.F.Tmp))
|
||||||
|
return false;
|
||||||
|
NumBytesReadButNoWritten++;
|
||||||
|
}
|
||||||
|
if (Buffer[StartIndex] == (int) Gbl.DelimiterString[0]) // First character identical
|
||||||
|
{
|
||||||
|
for (NumBytesIdentical = 1, i = (StartIndex + 1) % Gbl.LengthDelimiterString;
|
||||||
|
NumBytesIdentical < Gbl.LengthDelimiterString;
|
||||||
|
NumBytesIdentical++, i = (i + 1) % Gbl.LengthDelimiterString)
|
||||||
|
{
|
||||||
|
if (NumBytesReadButNoWritten == NumBytesIdentical) // Next character identical
|
||||||
|
{
|
||||||
|
Buffer[i] = fgetc (Gbl.F.Tmp); // Read next character
|
||||||
|
if (feof (Gbl.F.Tmp))
|
||||||
|
return false;
|
||||||
|
NumBytesReadButNoWritten++;
|
||||||
|
}
|
||||||
|
if (Buffer[i] != (int) Gbl.DelimiterString[NumBytesIdentical]) // Next different character
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (NumBytesIdentical == Gbl.LengthDelimiterString) // Str found
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
NumBytesReadButNoWritten--;
|
||||||
|
StartIndex = (StartIndex + 1) % Gbl.LengthDelimiterString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Not reached
|
||||||
|
}
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
/******************** Read from file until quote '\"' ************************/
|
/******************** Read from file until quote '\"' ************************/
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
/************************** Public types and constants ***********************/
|
/************************** Public types and constants ***********************/
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
#define Par_MAX_LENGTH_STR_DELIMIT 100
|
||||||
|
|
||||||
struct StartLength
|
struct StartLength
|
||||||
{
|
{
|
||||||
unsigned long Start;
|
unsigned long Start;
|
||||||
|
|
|
@ -2502,6 +2502,7 @@ If StrDelimit is not found, return -1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define MAX_LENGTH_STR_DELIMIT 100
|
#define MAX_LENGTH_STR_DELIMIT 100
|
||||||
|
|
||||||
int Str_ReceiveFileUntilDelimitStr (FILE *FileSrc, FILE *FileTgt, char *StrDst, const char *StrDelimit, unsigned long long MaxLength)
|
int Str_ReceiveFileUntilDelimitStr (FILE *FileSrc, FILE *FileTgt, char *StrDst, const char *StrDelimit, unsigned long long MaxLength)
|
||||||
{
|
{
|
||||||
int NumBytesIdentical, // Number of characters identical in each iteration of the loop
|
int NumBytesIdentical, // Number of characters identical in each iteration of the loop
|
||||||
|
@ -2581,55 +2582,6 @@ int Str_ReceiveFileUntilDelimitStr (FILE *FileSrc, FILE *FileTgt, char *StrDst,
|
||||||
return 0; // Not reached
|
return 0; // Not reached
|
||||||
}
|
}
|
||||||
|
|
||||||
int Str_SkipFileUntilDelimitStr (FILE *FileSrc, const char *StrDelimit)
|
|
||||||
{
|
|
||||||
int NumBytesIdentical; // Number of characters identical in each iteration of the loop
|
|
||||||
int NumBytesReadButNoWritten = 0; // Number of characters read from the source file
|
|
||||||
// and not written in the destination file
|
|
||||||
int LengthStrDelimit = strlen (StrDelimit);
|
|
||||||
int Buffer[MAX_LENGTH_STR_DELIMIT+1];
|
|
||||||
int StartIndex = 0;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (LengthStrDelimit == 0 ||
|
|
||||||
LengthStrDelimit > MAX_LENGTH_STR_DELIMIT)
|
|
||||||
Lay_ShowErrorAndExit ("Wrong delimiter string.");
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (!NumBytesReadButNoWritten)
|
|
||||||
{ // Read next character
|
|
||||||
Buffer[StartIndex] = fgetc (FileSrc);
|
|
||||||
if (feof (FileSrc))
|
|
||||||
return -1;
|
|
||||||
NumBytesReadButNoWritten++;
|
|
||||||
}
|
|
||||||
if (Buffer[StartIndex] == (int) StrDelimit[0]) // First character identical
|
|
||||||
{
|
|
||||||
for (NumBytesIdentical = 1, i = (StartIndex + 1) % LengthStrDelimit;
|
|
||||||
NumBytesIdentical < LengthStrDelimit;
|
|
||||||
NumBytesIdentical++, i = (i + 1) % LengthStrDelimit)
|
|
||||||
{
|
|
||||||
if (NumBytesReadButNoWritten == NumBytesIdentical) // Next character identical
|
|
||||||
{
|
|
||||||
Buffer[i] = fgetc (FileSrc); // Read next character
|
|
||||||
if (feof (FileSrc))
|
|
||||||
return -1;
|
|
||||||
NumBytesReadButNoWritten++;
|
|
||||||
}
|
|
||||||
if (Buffer[i] != (int) StrDelimit[NumBytesIdentical]) // Next different character
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (NumBytesIdentical == LengthStrDelimit) // Str found
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
NumBytesReadButNoWritten--;
|
|
||||||
StartIndex = (StartIndex+1) % LengthStrDelimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // Not reached
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
/****** Convert invalid characters in a file name to valid characters ********/
|
/****** Convert invalid characters in a file name to valid characters ********/
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
Loading…
Reference in New Issue