// swad_image.c: processing of image uploaded in a form /* 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-2017 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 3 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 *********************************/ /*****************************************************************************/ #include // For PATH_MAX #include // For boolean type #include // For exit, system, malloc, free, etc #include // For string functions #include // For the macro WEXITSTATUS #include // For unlink #include "swad_config.h" #include "swad_global.h" #include "swad_file.h" #include "swad_file_browser.h" #include "swad_image.h" /*****************************************************************************/ /****************************** Public constants *****************************/ /*****************************************************************************/ /*****************************************************************************/ /***************************** Internal constants ****************************/ /*****************************************************************************/ /*****************************************************************************/ /****************************** Internal types *******************************/ /*****************************************************************************/ /*****************************************************************************/ /************** External global variables from others modules ****************/ /*****************************************************************************/ extern struct Globals Gbl; /*****************************************************************************/ /************************* Internal global variables *************************/ /*****************************************************************************/ /*****************************************************************************/ /***************************** Internal prototypes ***************************/ /*****************************************************************************/ static void Img_ProcessImage (struct Image *Image, const char *FileNameImgOriginal, const char *FileNameImgProcessed); /*****************************************************************************/ /*************************** Reset image fields ******************************/ /*****************************************************************************/ // Every struct Image must be initialized with this function Img_ImageConstructor after it is declared // Every call to Img_ImageConstructor must have a call to Img_ImageDestructor void Img_ImageConstructor (struct Image *Image) { Img_ResetImageExceptTitleAndURL (Image); Image->Title = NULL; Image->URL = NULL; } /*****************************************************************************/ /***************** Reset image fields except title and URL *******************/ /*****************************************************************************/ void Img_ResetImageExceptTitleAndURL (struct Image *Image) { Image->Action = Img_ACTION_NO_IMAGE; Image->Status = Img_FILE_NONE; Image->Name[0] = '\0'; } /*****************************************************************************/ /******************************** Free image *********************************/ /*****************************************************************************/ // Every call to Img_ImageConstructor must have a call to Img_ImageDestructor void Img_ImageDestructor (struct Image *Image) { Img_FreeImageTitle (Image); Img_FreeImageURL (Image); } /*****************************************************************************/ /****************************** Free image title *****************************/ /*****************************************************************************/ void Img_FreeImageTitle (struct Image *Image) { // Image->Title must be initialized to NULL after declaration if (Image->Title) { free ((void *) Image->Title); Image->Title = NULL; } } /*****************************************************************************/ /******************************* Free image URL ******************************/ /*****************************************************************************/ void Img_FreeImageURL (struct Image *Image) { // Image->URL must be initialized to NULL after declaration if (Image->URL) { free ((void *) Image->URL); Image->URL = NULL; } } /*****************************************************************************/ /**** Get image name, title and URL from a query result and copy to struct ***/ /*****************************************************************************/ void Img_GetImageNameTitleAndURLFromRow (const char *Name, const char *Title, const char *URL, struct Image *Image) { size_t Length; /***** Copy image name to struct *****/ Str_Copy (Image->Name,Name, Img_BYTES_NAME); /***** Set status of image file *****/ Image->Status = Image->Name[0] ? Img_NAME_STORED_IN_DB : Img_FILE_NONE; /***** Copy image title to struct *****/ // Image->Title can be empty or filled with previous value // If filled ==> free it Img_FreeImageTitle (Image); if (Title[0]) { /* Get and limit length of the title */ Length = strlen (Title); if (Length > Img_MAX_BYTES_TITLE) Length = Img_MAX_BYTES_TITLE; if ((Image->Title = (char *) malloc (Length + 1)) == NULL) Lay_ShowErrorAndExit ("Error allocating memory for image title."); Str_Copy (Image->Title,Title, Length); } /***** Copy image URL to struct *****/ // Image->URL can be empty or filled with previous value // If filled ==> free it Img_FreeImageURL (Image); if (URL[0]) { /* Get and limit length of the URL */ Length = strlen (URL); if (Length > Img_MAX_BYTES_TITLE) Length = Img_MAX_BYTES_TITLE; if ((Image->URL = (char *) malloc (Length + 1)) == NULL) Lay_ShowErrorAndExit ("Error allocating memory for image URL."); Str_Copy (Image->URL,URL, Length); } } /*****************************************************************************/ /************ Draw input fields to upload an image inside a form *************/ /*****************************************************************************/ void Img_PutImageUploader (int NumImgInForm,const char *ClassImgTitURL) { extern const char *Txt_Image; extern const char *Txt_optional; extern const char *Txt_Image_title_attribution; extern const char *Txt_Link; struct ParamUploadImg ParamUploadImg; char Id[Act_MAX_BYTES_ID]; /***** Set names of parameters depending on number of image in form *****/ Img_SetParamNames (&ParamUploadImg,NumImgInForm); /***** Create unique id for this image uploader *****/ Act_SetUniqueId (Id); /***** Start container *****/ fprintf (Gbl.F.Out,"
"); /***** Action to perform on image *****/ Par_PutHiddenParamUnsigned (ParamUploadImg.Action,(unsigned) Img_ACTION_NEW_IMAGE); /***** Image file *****/ fprintf (Gbl.F.Out,"", Gbl.Prefs.IconsURL, Txt_Image,Txt_Image,Txt_optional, ParamUploadImg.File, Id,Id); /***** Image title/attribution and URL *****/ fprintf (Gbl.F.Out,"
", Id); fprintf (Gbl.F.Out,"", ParamUploadImg.Title, Txt_Image_title_attribution,Txt_optional, ClassImgTitURL,Img_MAX_CHARS_TITLE); fprintf (Gbl.F.Out,"
" "", ParamUploadImg.URL, Txt_Link,Txt_optional, ClassImgTitURL,Cns_MAX_CHARS_WWW); fprintf (Gbl.F.Out,"
"); /***** End container *****/ fprintf (Gbl.F.Out,"
"); } /*****************************************************************************/ /***************************** Get image from form ***************************/ /*****************************************************************************/ // If NumImgInForm < 0, params have no suffix // If NumImgInForm >= 0, the number is a suffix of the params void Img_GetImageFromForm (int NumImgInForm,struct Image *Image, void (*GetImageFromDB) (int NumImgInForm,struct Image *Image)) { struct ParamUploadImg ParamUploadImg; char Title[Img_MAX_BYTES_TITLE + 1]; char URL[Cns_MAX_BYTES_WWW + 1]; size_t Length; /***** Set names of parameters depending on number of image in form *****/ Img_SetParamNames (&ParamUploadImg,NumImgInForm); /***** First, get action and initialize image (except title, that will be get after the image file) *****/ Image->Action = Img_GetImageActionFromForm (ParamUploadImg.Action); Image->Status = Img_FILE_NONE; Image->Name[0] = '\0'; /***** Secondly, get the image name and the file *****/ switch (Image->Action) { case Img_ACTION_NEW_IMAGE: // Upload new image /***** Get new image (if present ==> process and create temporary file) *****/ Img_GetAndProcessImageFileFromForm (Image,ParamUploadImg.File); switch (Image->Status) { case Img_FILE_NONE: // No new image received Image->Action = Img_ACTION_NO_IMAGE; Image->Name[0] = '\0'; break; case Img_FILE_RECEIVED: // New image received, but not processed Image->Status = Img_FILE_NONE; Image->Name[0] = '\0'; break; default: break; } break; case Img_ACTION_KEEP_IMAGE: // Keep current image unchanged /***** Get image name *****/ if (GetImageFromDB != NULL) GetImageFromDB (NumImgInForm,Image); break; case Img_ACTION_CHANGE_IMAGE: // Replace old image by new image /***** Get new image (if present ==> process and create temporary file) *****/ Img_GetAndProcessImageFileFromForm (Image,ParamUploadImg.File); if (Image->Status != Img_FILE_PROCESSED && // No new image received-processed successfully GetImageFromDB != NULL) /* Get image name */ GetImageFromDB (NumImgInForm,Image); break; case Img_ACTION_NO_IMAGE: // Do not use image (remove current image if exists) break; } /***** Third, get image title from form *****/ Par_GetParToText (ParamUploadImg.Title,Title,Img_MAX_BYTES_TITLE); /* If the title coming from the form is empty, keep current image title unchanged If not empty, copy it to current image title */ if ((Length = strlen (Title)) > 0) { /* Overwrite current title (empty or coming from database) with the title coming from the form */ Img_FreeImageTitle (Image); if ((Image->Title = (char *) malloc (Length + 1)) == NULL) Lay_ShowErrorAndExit ("Error allocating memory for image title."); Str_Copy (Image->Title,Title, Length); } /***** By last, get image URL from form *****/ Par_GetParToText (ParamUploadImg.URL,URL,Cns_MAX_BYTES_WWW); /* If the URL coming from the form is empty, keep current image URL unchanged If not empty, copy it to current image URL */ if ((Length = strlen (URL)) > 0) { /* Overwrite current URL (empty or coming from database) with the URL coming from the form */ Img_FreeImageURL (Image); if ((Image->URL = (char *) malloc (Length + 1)) == NULL) Lay_ShowErrorAndExit ("Error allocating memory for image URL."); Str_Copy (Image->URL,URL, Length); } } /*****************************************************************************/ /********* Set parameters names depending on number of image in form *********/ /*****************************************************************************/ // If NumImgInForm < 0, params have no suffix // If NumImgInForm >= 0, the number is a suffix of the params void Img_SetParamNames (struct ParamUploadImg *ParamUploadImg,int NumImgInForm) { if (NumImgInForm < 0) // One unique image in form ==> no suffix needed { Str_Copy (ParamUploadImg->Action,"ImgAct", Img_MAX_BYTES_PARAM_UPLOAD_IMG); Str_Copy (ParamUploadImg->File ,"ImgFil", Img_MAX_BYTES_PARAM_UPLOAD_IMG); Str_Copy (ParamUploadImg->Title ,"ImgTit", Img_MAX_BYTES_PARAM_UPLOAD_IMG); Str_Copy (ParamUploadImg->URL ,"ImgURL", Img_MAX_BYTES_PARAM_UPLOAD_IMG); } else // Several images in form ==> add suffix { sprintf (ParamUploadImg->Action,"ImgAct%u",NumImgInForm); sprintf (ParamUploadImg->File ,"ImgFil%u",NumImgInForm); sprintf (ParamUploadImg->Title ,"ImgTit%u",NumImgInForm); sprintf (ParamUploadImg->URL ,"ImgURL%u",NumImgInForm); } } /*****************************************************************************/ /************************* Get image action from form ************************/ /*****************************************************************************/ Img_Action_t Img_GetImageActionFromForm (const char *ParamAction) { /***** Get parameter with the action to perform on image *****/ return (Img_Action_t) Par_GetParToUnsignedLong (ParamAction, 0, Img_NUM_ACTIONS - 1, (unsigned long) Img_ACTION_DEFAULT); } /*****************************************************************************/ /**************************** Get image from form ****************************/ /*****************************************************************************/ void Img_GetAndProcessImageFileFromForm (struct Image *Image,const char *ParamFile) { struct Param *Param; char FileNameImgSrc[PATH_MAX + 1]; char *PtrExtension; size_t LengthExtension; char MIMEType[Brw_MAX_BYTES_MIME_TYPE + 1]; char PathImgPriv[PATH_MAX + 1]; char FileNameImgOrig[PATH_MAX + 1]; // Full name of original uploaded file char FileNameImgTmp[PATH_MAX + 1]; // Full name of temporary processed file bool WrongType = false; /***** Rest image file status *****/ Image->Status = Img_FILE_NONE; /***** Get filename and MIME type *****/ Param = Fil_StartReceptionOfFile (ParamFile,FileNameImgSrc,MIMEType); if (!FileNameImgSrc[0]) // No file present return; /* Get filename extension */ if ((PtrExtension = strrchr (FileNameImgSrc,(int) '.')) == NULL) return; LengthExtension = strlen (PtrExtension); if (LengthExtension < Fil_MIN_BYTES_FILE_EXTENSION || LengthExtension > Fil_MAX_BYTES_FILE_EXTENSION) return; /* Check if the file type is image/ or application/octet-stream */ if (strncmp (MIMEType,"image/",strlen ("image/"))) if (strcmp (MIMEType,"application/octet-stream")) if (strcmp (MIMEType,"application/octetstream")) if (strcmp (MIMEType,"application/octet")) WrongType = true; if (WrongType) return; /***** Assign a unique name for the image *****/ Cry_CreateUniqueNameEncrypted (Image->Name); /***** Create private directories if not exist *****/ /* Create private directory for images if it does not exist */ sprintf (PathImgPriv,"%s/%s", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG); Fil_CreateDirIfNotExists (PathImgPriv); /* Create temporary private directory for images if it does not exist */ sprintf (PathImgPriv,"%s/%s/%s", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG,Cfg_FOLDER_IMG_TMP); Fil_CreateDirIfNotExists (PathImgPriv); /***** Remove old temporary private files *****/ Fil_RemoveOldTmpFiles (PathImgPriv,Cfg_TIME_TO_DELETE_IMAGES_TMP_FILES,false); /***** End the reception of original not processed image (it can be very big) into a temporary file *****/ Image->Status = Img_FILE_NONE; sprintf (FileNameImgOrig,"%s/%s/%s/%s_original.%s", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG,Cfg_FOLDER_IMG_TMP, Image->Name,PtrExtension); if (Fil_EndReceptionOfFile (FileNameImgOrig,Param)) // Success { Image->Status = Img_FILE_RECEIVED; /***** Convert original image to temporary JPEG processed file by calling to program that makes the conversion *****/ sprintf (FileNameImgTmp,"%s/%s/%s/%s.jpg", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG,Cfg_FOLDER_IMG_TMP, Image->Name); Img_ProcessImage (Image,FileNameImgOrig,FileNameImgTmp); Image->Status = Img_FILE_PROCESSED; /***** Remove temporary original file *****/ unlink (FileNameImgOrig); } } /*****************************************************************************/ /************ Process original image generating processed image **************/ /*****************************************************************************/ static void Img_ProcessImage (struct Image *Image, const char *FileNameImgOriginal, const char *FileNameImgProcessed) { char Command[1024 + PATH_MAX * 2]; int ReturnCode; sprintf (Command,"convert %s -resize '%ux%u>' -quality %u %s", FileNameImgOriginal, Image->Width, Image->Height, Image->Quality, FileNameImgProcessed); ReturnCode = system (Command); if (ReturnCode == -1) Lay_ShowErrorAndExit ("Error when running command to process image."); /***** Write message depending on return code *****/ ReturnCode = WEXITSTATUS(ReturnCode); if (ReturnCode != 0) { sprintf (Gbl.Alert.Txt,"Image could not be processed successfully.
" "Error code returned by the program of processing: %d", ReturnCode); Lay_ShowErrorAndExit (Gbl.Alert.Txt); } } /*****************************************************************************/ /**** Move temporary processed image file to definitive private directory ****/ /*****************************************************************************/ void Img_MoveImageToDefinitiveDirectory (struct Image *Image) { char PathImgPriv[PATH_MAX + 1]; char FileNameImgTmp[PATH_MAX + 1]; // Full name of temporary processed file char FileNameImg[PATH_MAX + 1]; // Full name of definitive processed file /***** Create subdirectory if it does not exist *****/ sprintf (PathImgPriv,"%s/%s/%c%c", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG, Image->Name[0], Image->Name[1]); Fil_CreateDirIfNotExists (PathImgPriv); /***** Temporary processed file *****/ sprintf (FileNameImgTmp,"%s/%s/%s/%s.jpg", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG,Cfg_FOLDER_IMG_TMP, Image->Name); /***** Definitive processed file *****/ sprintf (FileNameImg,"%s/%s/%c%c/%s.jpg", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG, Image->Name[0], Image->Name[1], Image->Name); /***** Move file *****/ if (rename (FileNameImgTmp,FileNameImg)) // Fail Ale_ShowAlert (Ale_ERROR,"Can not move file."); else // Success Image->Status = Img_FILE_MOVED; } /*****************************************************************************/ /******************** Write the image of a test question *********************/ /*****************************************************************************/ void Img_ShowImage (struct Image *Image, const char *ClassContainer,const char *ClassImg) { extern const char *Txt_Image_not_found; char FileNameImgPriv[PATH_MAX + 1]; char FullPathImgPriv[PATH_MAX + 1]; char URL[PATH_MAX + 1]; bool PutLink; /***** If no image to show ==> nothing to do *****/ if (!Image->Name) return; if (!Image->Name[0]) return; if (Image->Status != Img_NAME_STORED_IN_DB) return; /***** Create a temporary public directory used to show the image *****/ Brw_CreateDirDownloadTmp (); /***** Build private path to image *****/ sprintf (FileNameImgPriv,"%s.jpg",Image->Name); sprintf (FullPathImgPriv,"%s/%s/%c%c/%s", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG, Image->Name[0], Image->Name[1], FileNameImgPriv); /***** Check if private image file exists *****/ if (Fil_CheckIfPathExists (FullPathImgPriv)) { /***** Create symbolic link from temporary public directory to private file in order to gain access to it for showing/downloading *****/ Brw_CreateTmpPublicLinkToPrivateFile (FullPathImgPriv,FileNameImgPriv); /***** Create URL pointing to symbolic link *****/ sprintf (URL,"%s/%s/%s/%s", Cfg_URL_SWAD_PUBLIC,Cfg_FOLDER_FILE_BROWSER_TMP, Gbl.FileBrowser.TmpPubDir, FileNameImgPriv); /***** Show image *****/ /* Check if optional link is present */ PutLink = false; if (Image->URL) if (Image->URL[0]) PutLink = true; /* Start image container */ fprintf (Gbl.F.Out,""); } else Ale_ShowAlert (Ale_WARNING,Txt_Image_not_found); } /*****************************************************************************/ /** Remove private file with an image, given the image name (without .jpg) ***/ /*****************************************************************************/ void Img_RemoveImageFile (const char *ImageName) { char FullPathImgPriv[PATH_MAX + 1]; if (ImageName[0]) { /***** Build path to private file *****/ sprintf (FullPathImgPriv,"%s/%s/%c%c/%s.jpg", Cfg_PATH_SWAD_PRIVATE,Cfg_FOLDER_IMG, ImageName[0], ImageName[1], ImageName); /***** Remove private file *****/ unlink (FullPathImgPriv); // Public links are removed automatically after a period } }