// swad_autolink.c: inserting automatic links in text /* 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-2021 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 . */ /*****************************************************************************/ /********************************* Headers ***********************************/ /*****************************************************************************/ #define _GNU_SOURCE // For asprintf #include // For tolower #include // For NULL #include // For asprintf #include // For malloc and free #include // For string functions #include "swad_autolink.h" #include "swad_config.h" #include "swad_error.h" #include "swad_form.h" #include "swad_global.h" #include "swad_nickname.h" #include "swad_nickname_database.h" #include "swad_photo.h" #include "swad_string.h" #include "swad_user.h" /*****************************************************************************/ /******************************* Private types *******************************/ /*****************************************************************************/ typedef enum { ALn_LINK_UNKNOWN = 0, ALn_LINK_URL = 1, ALn_LINK_NICK = 2, } ALn_LinkType_t; struct ALn_Substring { char *Str; // Pointer to the first char of substring size_t Len; // Length of the substring }; struct ALn_Link { ALn_LinkType_t Type; struct ALn_Substring URLorNick; struct { struct ALn_Substring Anchor1; struct ALn_Substring Anchor2; struct ALn_Substring Anchor3; } Nick; size_t AddedLengthUntilHere; // Total length of extra HTML code added until this link (included) struct ALn_Link *Prev; struct ALn_Link *Next; }; /*****************************************************************************/ /******************** Global variables from other modules ********************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /*************************** Private prototypes ******************************/ /*****************************************************************************/ static void ALn_CreateFirstLink (struct ALn_Link **Link, struct ALn_Link **LastLink); static void ALn_CreateNextLink (struct ALn_Link **Link, struct ALn_Link **LastLink); static void ALn_FreeLinks (struct ALn_Link *LastLink); static ALn_LinkType_t ALn_CheckURL (char **PtrSrc,char PrevCh, struct ALn_Link **Link, struct ALn_Link **LastLink, size_t MaxCharsURLOnScreen); static ALn_LinkType_t ALn_CheckNickname (char **PtrSrc,char PrevCh, struct ALn_Link **Link, struct ALn_Link **LastLink); static void ALn_CopySubstring (const struct ALn_Substring *PtrSrc,char **PtrDst); /*****************************************************************************/ /**************************** Private constants ******************************/ /*****************************************************************************/ // For URLs the length of anchor is fixed, so it can be calculated once #define ALn_URL_ANCHOR_1 "" #define ALn_URL_ANCHOR_3 "" #define ALn_URL_ANCHOR_1_LENGTH (sizeof (ALn_URL_ANCHOR_1) - 1) #define ALn_URL_ANCHOR_2_LENGTH (sizeof (ALn_URL_ANCHOR_2) - 1) #define ALn_URL_ANCHOR_3_LENGTH (sizeof (ALn_URL_ANCHOR_3) - 1) #define ALn_URL_ANCHOR_TOTAL_LENGTH (ALn_URL_ANCHOR_1_LENGTH + ALn_URL_ANCHOR_2_LENGTH + ALn_URL_ANCHOR_3_LENGTH) static const struct ALn_Substring URLAnchor1 = { .Str = ALn_URL_ANCHOR_1, .Len = ALn_URL_ANCHOR_1_LENGTH, }; static const struct ALn_Substring URLAnchor2 = { .Str = ALn_URL_ANCHOR_2, .Len = ALn_URL_ANCHOR_2_LENGTH, }; static const struct ALn_Substring URLAnchor3 = { .Str = ALn_URL_ANCHOR_3, .Len = ALn_URL_ANCHOR_3_LENGTH, }; /*****************************************************************************/ /************** Insert automatic links in every URL or nickname **************/ /*****************************************************************************/ /* Insertion example: The web site of @rms is https://stallman.org/ The web site of @rms is https://stallman.org/ */ /*
@acanas
*/ void ALn_InsertLinks (char *Txt,unsigned long MaxLength,size_t MaxCharsURLOnScreen) { size_t TxtLength; char PrevCh = '\0'; char *PtrSrc; char *PtrDst; struct ALn_Link *Link; struct ALn_Link *LastLink; size_t Length; size_t i; struct ALn_Substring Limited; // URL displayed on screen (may be shorter than actual length) const struct ALn_Substring *Anchor1; const struct ALn_Substring *Anchor2; const struct ALn_Substring *Anchor3; /**************************************************************/ /***** Find starts and ends of links (URLs and nicknames) *****/ /**************************************************************/ ALn_CreateFirstLink (&Link,&LastLink); for (PtrSrc = Txt; *PtrSrc;) /* Check if the next char is the start of a URL */ if ((Link->Type = ALn_CheckURL (&PtrSrc,PrevCh, &Link,&LastLink, MaxCharsURLOnScreen)) == ALn_LINK_UNKNOWN) /* Check if the next char is the start of a nickname */ if ((Link->Type = ALn_CheckNickname (&PtrSrc,PrevCh, &Link,&LastLink)) == ALn_LINK_UNKNOWN) { /* The next char is not the start of a URL or a nickname */ PrevCh = *PtrSrc; PtrSrc++; } /**********************************************************************/ /***** If there are one or more links (URLs or nicknames) in text *****/ /**********************************************************************/ if (LastLink) // Not null ==> one or more links found { /***** Insert links from end to start of text, only if there is enough space available in text *****/ TxtLength = strlen (Txt); if (TxtLength + LastLink->AddedLengthUntilHere <= MaxLength) for (Link = LastLink; Link; Link = Link->Prev) { /***** Set anchors *****/ switch (Link->Type) { case ALn_LINK_URL: Anchor1 = &URLAnchor1; Anchor2 = &URLAnchor2; Anchor3 = &URLAnchor3; break; case ALn_LINK_NICK: Anchor1 = &Link->Nick.Anchor1; Anchor2 = &Link->Nick.Anchor2; Anchor3 = &Link->Nick.Anchor3; break; default: continue; } /***** 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) *****/ PtrSrc = (Link == LastLink) ? Txt + TxtLength : Link->Next->URLorNick.Str - 1, PtrDst = PtrSrc + Link->AddedLengthUntilHere, Length = PtrSrc - (Link->URLorNick.Str + Link->URLorNick.Len - 1); for (i = 0; i < Length; i++) *PtrDst-- = *PtrSrc--; /***** Step 2: Copy third part of anchor *****/ ALn_CopySubstring (Anchor3,&PtrDst); /***** Step 3: Move forward the link (URL or nickname) to be shown on screen *****/ switch (Link->Type) { case ALn_LINK_URL: if (Link->URLorNick.Len <= MaxCharsURLOnScreen) ALn_CopySubstring (&Link->URLorNick,&PtrDst); else { /* Limit the length of URL */ if ((Limited.Str = malloc (Link->URLorNick.Len + 1)) == NULL) Err_NotEnoughMemoryExit (); strncpy (Limited.Str,Link->URLorNick.Str,Link->URLorNick.Len); Limited.Str[Link->URLorNick.Len] = '\0'; Limited.Len = Str_LimitLengthHTMLStr (Limited.Str,MaxCharsURLOnScreen); ALn_CopySubstring (&Limited,&PtrDst); free (Limited.Str); } break; case ALn_LINK_NICK: ALn_CopySubstring (&Link->URLorNick,&PtrDst); break; default: break; } /***** Step 4: Copy second part of anchor *****/ ALn_CopySubstring (Anchor2,&PtrDst); /***** Step 5: Copy link into directive A (it's mandatory to do the copy in reverse order to avoid overwriting source URL or nickname) *****/ ALn_CopySubstring (&Link->URLorNick,&PtrDst); /***** Step 6: Copy first part of anchor *****/ ALn_CopySubstring (Anchor1,&PtrDst); } } /***********************************/ /***** Free memory for anchors *****/ /***********************************/ ALn_FreeLinks (LastLink); } /***************************** Create first link ******************************/ static void ALn_CreateFirstLink (struct ALn_Link **Link, struct ALn_Link **LastLink) { /***** Reset last link pointer *****/ (*LastLink) = NULL; /***** Allocate current link *****/ if (((*Link) = malloc (sizeof (struct ALn_Link))) == NULL) Err_NotEnoughMemoryExit (); /***** Initialize current link *****/ (*Link)->Prev = NULL; (*Link)->Next = NULL; } /***************************** Create next link ******************************/ static void ALn_CreateNextLink (struct ALn_Link **Link, struct ALn_Link **LastLink) { /***** Current link now is pointing to a correct link, so set last link pointer to current link *****/ (*LastLink) = (*Link); /***** Allocate next link *****/ if (((*Link)->Next = malloc (sizeof (struct ALn_Link))) == NULL) Err_NotEnoughMemoryExit (); /***** Initialize next link *****/ (*Link)->Next->Prev = *Link; (*Link)->Next->Next = NULL; /***** Change current link to just allocated link *****/ (*Link) = (*Link)->Next; } /***************************** Free found links ******************************/ static void ALn_FreeLinks (struct ALn_Link *LastLink) { struct ALn_Link *Link; struct ALn_Link *PrevLink; for (Link = LastLink; Link; Link = PrevLink) { PrevLink = Link->Prev; if (Link->Type == ALn_LINK_NICK) { if (Link->Nick.Anchor3.Str) free (Link->Nick.Anchor3.Str); if (Link->Nick.Anchor2.Str) free (Link->Nick.Anchor2.Str); if (Link->Nick.Anchor1.Str) free (Link->Nick.Anchor1.Str); } free (Link); } } /**************************** Check if a URL found ***************************/ static ALn_LinkType_t ALn_CheckURL (char **PtrSrc,char PrevCh, struct ALn_Link **Link, struct ALn_Link **LastLink, size_t MaxCharsURLOnScreen) { unsigned char Ch; size_t NumChars1; size_t NumChars2; char *PtrEnd; // Pointer to the last char of URL/nickname in original text char *Limited; // URL displayed on screen (may be shorter than actual length) ALn_LinkType_t Type = ALn_LINK_UNKNOWN; /***** Check if the next char is the start of a URL *****/ if (tolower ((int) *(*PtrSrc)) == (int) 'h') if (!Str_ChIsAlphaNum (PrevCh)) { (*Link)->URLorNick.Str = (*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://... Type = ALn_LINK_URL; } else if (tolower ((int) *(*PtrSrc)) == (int) 's') // https... { if (*++(*PtrSrc) == ':') // https:... { if (*++(*PtrSrc) == '/') // https:/... if (*++(*PtrSrc) == '/') // https://... Type = ALn_LINK_URL; } } } } } if (Type == ALn_LINK_URL) { /***** 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 == '"') { 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 == '"') { PtrEnd = (*PtrSrc) - NumChars2 - NumChars1 - 1; break; } } } /***** Compute number of bytes added until here *****/ (*Link)->AddedLengthUntilHere = (*Link)->Prev ? (*Link)->Prev->AddedLengthUntilHere : 0; (*Link)->URLorNick.Len = (size_t) (PtrEnd + 1 - (*Link)->URLorNick.Str); if ((*Link)->URLorNick.Len <= MaxCharsURLOnScreen) (*Link)->AddedLengthUntilHere += ALn_URL_ANCHOR_TOTAL_LENGTH + (*Link)->URLorNick.Len; else // If URL is too long to be displayed ==> short it { if ((Limited = malloc ((*Link)->URLorNick.Len + 1)) == NULL) Err_NotEnoughMemoryExit (); strncpy (Limited,(*Link)->URLorNick.Str,(*Link)->URLorNick.Len); Limited[(*Link)->URLorNick.Len] = '\0'; (*Link)->AddedLengthUntilHere += ALn_URL_ANCHOR_TOTAL_LENGTH + Str_LimitLengthHTMLStr (Limited,MaxCharsURLOnScreen); free (Limited); } /***** Create next link *****/ ALn_CreateNextLink (Link,LastLink); } } return Type; } /************************* Check if a nickname found *************************/ static ALn_LinkType_t ALn_CheckNickname (char **PtrSrc,char PrevCh, struct ALn_Link **Link, struct ALn_Link **LastLink) { extern const char *Lan_STR_LANG_ID[1 + Lan_NUM_LANGUAGES]; char Ch; size_t Length; char ParamsStr[Frm_MAX_BYTES_PARAMS_STR]; struct UsrData UsrDat; bool ShowPhoto = false; char PhotoURL[PATH_MAX + 1]; char *CaptionStr; char *ImgStr; char NickWithoutArr[Nck_MAX_BYTES_NICK_WITHOUT_ARROBA + 1]; bool NickSeemsValid; ALn_LinkType_t Type = ALn_LINK_UNKNOWN; /***** Check if the next char is the start of a nickname *****/ Ch = *(*PtrSrc); if (Ch == '@') // Current is @ if (!Str_ChIsAlphaNum (PrevCh)) // Previous is not alphanumeric if (Str_ChIsAlphaNum (*((*PtrSrc) + 1))) // Next is alphanumeric { (*Link)->URLorNick.Str = (*PtrSrc); /***** Find nickname end *****/ (*PtrSrc)++; // Points to first character after @ /***** A nick can have digits, letters and '_' *****/ for (; *(*PtrSrc); (*PtrSrc)++) { Ch = *(*PtrSrc); if (!Str_ChIsAlphaNum (Ch)) break; } /***** Calculate length of this nickname *****/ (*Link)->URLorNick.Len = (size_t) ((*PtrSrc) - (*Link)->URLorNick.Str); /***** A nick (without arroba) must have a number of characters Nck_MIN_CHARS_NICK_WITHOUT_ARROBA <= Length <= Nck_MAX_CHARS_NICK_WITHOUT_ARROBA *****/ Length = (*Link)->URLorNick.Len - 1; // Do not count the initial @ NickSeemsValid = Length >= Nck_MIN_CHARS_NICK_WITHOUT_ARROBA && Length <= Nck_MAX_CHARS_NICK_WITHOUT_ARROBA; if (NickSeemsValid) { /***** Get user's code using nickname *****/ Usr_UsrDataConstructor (&UsrDat); strncpy (NickWithoutArr,(*Link)->URLorNick.Str + 1,Length); NickWithoutArr[Length] = '\0'; if ((UsrDat.UsrCod = Nck_DB_GetUsrCodFromNickname (NickWithoutArr)) > 0) { Type = ALn_LINK_NICK; Usr_GetUsrDataFromUsrCod (&UsrDat, Usr_DONT_GET_PREFS, Usr_DONT_GET_ROLE_IN_CURRENT_CRS); } if (Type == ALn_LINK_NICK) { /***** Reset anchors (checked on freeing) *****/ (*Link)->Nick.Anchor1.Str = (*Link)->Nick.Anchor2.Str = (*Link)->Nick.Anchor3.Str = NULL; /***** Create id for this form *****/ Gbl.Form.Num++; if (Gbl.Usrs.Me.Logged) snprintf (Gbl.Form.UniqueId,sizeof (Gbl.Form.UniqueId), "form_%s_%d",Gbl.UniqueNameEncrypted,Gbl.Form.Num); else snprintf (Gbl.Form.Id,sizeof (Gbl.Form.Id), "form_%d",Gbl.Form.Num); /***** Store first part of anchor *****/ Frm_SetParamsForm (ParamsStr,ActSeeOthPubPrf,true); if (asprintf (&(*Link)->Nick.Anchor1.Str, "
" "%s" // Parameters "Nick.Anchor1.Len = strlen ((*Link)->Nick.Anchor1.Str); /***** Store second part of anchor *****/ if (asprintf (&(*Link)->Nick.Anchor2.Str, "\">" "", Gbl.Usrs.Me.Logged ? Gbl.Form.UniqueId : Gbl.Form.Id) < 0) Err_NotEnoughMemoryExit (); (*Link)->Nick.Anchor2.Len = strlen ((*Link)->Nick.Anchor2.Str); /***** Store third part of anchor *****/ ShowPhoto = Pho_ShowingUsrPhotoIsAllowed (&UsrDat,PhotoURL); Pho_BuildHTMLUsrPhoto (&UsrDat,ShowPhoto ? PhotoURL : NULL, "PHOTO12x16",Pho_ZOOM, &CaptionStr, &ImgStr); if (asprintf (&(*Link)->Nick.Anchor3.Str, "
%s%s", CaptionStr, ImgStr) < 0) Err_NotEnoughMemoryExit (); free (ImgStr); free (CaptionStr); (*Link)->Nick.Anchor3.Len = strlen ((*Link)->Nick.Anchor3.Str); /***** Free memory used for user's data *****/ Usr_UsrDataDestructor (&UsrDat); /***** Compute number of bytes added until here *****/ (*Link)->AddedLengthUntilHere = (*Link)->Prev ? (*Link)->Prev->AddedLengthUntilHere : 0; (*Link)->AddedLengthUntilHere += (*Link)->Nick.Anchor1.Len + (*Link)->URLorNick.Len + (*Link)->Nick.Anchor2.Len + (*Link)->Nick.Anchor3.Len; /***** Create next link *****/ ALn_CreateNextLink (Link,LastLink); } } } return Type; } /************** Copy source substring to destination, backwards **************/ static void ALn_CopySubstring (const struct ALn_Substring *Src,char **Dst) { char *PtrSrc; // Local pointer optimizes access to memory char *PtrDst; // Local pointer optimizes access to memory size_t Len = Src->Len; /* Example: Src->Str = "Len = 9 Src->Str | _v_________________ |<|a|_|h|r|e|f|=|"| | | | | | | | | | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | | | | | | | | | _______v_v_v_v_v_v_v_v_v_____________ |_|_|_|<|a|_|h|r|e|f|=|"|_|_|_|_|_|_| ^ ^ | | PtrDst (*Dst) The copy has to be done backwards to avoid overwriting the original string */ if (Len) { PtrSrc = Src->Str + Len - 1; PtrDst = *Dst; // Make a local copy of destination pointer for (; Len; Len--) *PtrDst-- = *PtrSrc--; *Dst = PtrDst; // Update destination pointer } }