blob: 6c1a1bc12e8d8c0d8ba7e85ff53fad1a6949276b [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "DFPlatform.h"
#include "DFFilesystem.h"
#include "DFString.h"
#include "DFArray.h"
#include "DFBuffer.h"
#include <DocFormats/DFError.h>
#include "DFCharacterSet.h"
#include "DFCommon.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
int DFFileExists(const char *path)
{
struct stat statbuf;
return (0 == stat(path,&statbuf));
}
int DFIsDirectory(const char *path)
{
struct stat statbuf;
return ((0 == stat(path,&statbuf)) && S_ISDIR(statbuf.st_mode));
}
int DFCreateDirectory(const char *path, int intermediates, DFError **error)
{
size_t len = strlen(path);
for (size_t pos = 0; pos <= len; pos++) {
if ((pos == len) || (intermediates && (path[pos] == '/'))) {
if (pos == 0)
continue;
char *partial = DFSubstring(path,0,pos);
char *errmsg = NULL;
int ok = DFMkdirIfAbsent(partial,&errmsg);
free(partial);
if (!ok) {
DFErrorFormat(error,"%s",errmsg);
free(errmsg);
return 0;
}
}
}
return 1;
}
int DFEmptyDirectory(const char *path, DFError **error)
{
if (DFFileExists(path) && !DFDeleteFile(path,error))
return 0;
return DFCreateDirectory(path,1,error);
}
int DFCopyFile(const char *srcPath, const char *destPath, DFError **error)
{
FILE *srcFile = fopen(srcPath,"rb");
if (srcFile == NULL) {
DFErrorSetPosix(error,errno);
return 0;
}
FILE *destFile = fopen(destPath,"wb");
if (destFile == NULL) {
DFErrorSetPosix(error,errno);
fclose(srcFile);
return 0;
}
char buf[4096];
size_t r;
while ((r = fread(buf,1,4096,srcFile)) > 0)
fwrite(buf,1,r,destFile);
fclose(srcFile);
fclose(destFile);
return 1;
}
static int DFDeleteSingle(const char *path, DFError **error)
{
struct stat statbuf;
if (0 != stat(path,&statbuf)) {
DFErrorSetPosix(error,errno);
return 0;
}
if (S_ISDIR(statbuf.st_mode)) {
if (0 != rmdir(path)) {
DFErrorSetPosix(error,errno);
return 0;
}
}
else {
if (0 != unlink(path)) {
DFErrorSetPosix(error,errno);
return 0;
}
}
return 1;
}
int DFDeleteFile(const char *path, DFError **error)
{
if (path == NULL)
return 1;
// Determine if path refers to a file or directory. If it does not exist, or is inaccessible,
// an error will be returned.
struct stat statbuf;
if (0 != stat(path,&statbuf)) {
DFErrorSetPosix(error,errno);
return 0;
}
int ok = 1;
if (S_ISDIR(statbuf.st_mode)) {
const char **contents = DFContentsOfDirectory(path,1,error);
if (contents == NULL)
return 0;;
size_t count = DFStringArrayCount(contents);
for (size_t i = count; ok && (i > 0); i--) {
const char *relPath = contents[i-1];
char *fullPath = DFAppendPathComponent(path,relPath);
if (!DFDeleteSingle(fullPath,error)) {
DFErrorFormat(error,"%s: %s",relPath,DFErrorMessage(error));
ok = 0;
}
free(fullPath);
}
free(contents);
if (ok && !DFDeleteSingle(path,error))
ok = 0;
}
else {
if (0 != unlink(path)) {
DFErrorSetPosix(error,errno);
ok = 0;
}
}
return ok;
}
static void freeDirEntryList(DFDirEntryList *list)
{
DFDirEntryList *next;
for (DFDirEntryList *l = list; l != NULL; l = next) {
next = l->next;
free(l->name);
free(l);
}
}
static DFArray *arrayFromDirEntryList(DFDirEntryList *list)
{
DFArray *array = DFArrayNew((DFCopyFunction)xstrdup,(DFFreeFunction)free);
for (DFDirEntryList *l = list; l != NULL; l = l->next)
DFArrayAppend(array,l->name);
return array;
}
const char **DFContentsOfDirectory(const char *path, int recursive, DFError **error)
{
DFDirEntryList *list = NULL;
DFDirEntryList **listptr = &list;
char *errmsg = NULL;
if (!DFAddDirContents(path,"",recursive,&listptr,&errmsg)) {
DFErrorFormat(error,"%s",errmsg);
free(errmsg);
freeDirEntryList(list);
return NULL;
}
DFArray *array = arrayFromDirEntryList(list);
const char **entries = DFStringArrayFlatten(array);
DFArrayRelease(array);
freeDirEntryList(list);
return entries;
}
int DFPathContentsEqual(const char *path1, const char *path2)
{
DFBuffer *content1 = DFBufferReadFromFile(path1,NULL);
DFBuffer *content2 = DFBufferReadFromFile(path2,NULL);
int r = ((content1 != NULL) && (content2 != NULL) &&
(content1->len == content2->len) &&
!memcmp(content1->data,content2->data,content1->len));
DFBufferRelease(content1);
DFBufferRelease(content2);
return r;
}
char *DFPathDirName(const char *path)
{
size_t len = strlen(path);
if ((len > 0) && (path[len-1] == '/'))
len--;;
size_t pos = len;
while (1) {
if (pos == 0)
return xstrdup("");
if (path[pos-1] == '/') {
if (pos == 1)
return xstrdup("/");
else
return DFSubstring(path,0,pos-1);
}
pos--;
}
}
char *DFPathBaseName(const char *path)
{
size_t len = strlen(path);
if ((len > 0) && (path[len-1] == '/'))
len--;;
size_t pos = len;
while (1) {
if (pos == 0)
return DFSubstring(path,0,len);
if (path[pos-1] == '/') {
if (pos == 1)
return DFSubstring(path,1,len);
else
return DFSubstring(path,pos,len);
}
pos--;
}
}
char *DFPathExtension(const char *path)
{
size_t len = strlen(path);
if ((len > 0) && (path[len-1] == '/'))
len--;;
size_t pos = len;
while (1) {
if (pos == 0)
return xstrdup("");
if (path[pos-1] == '/')
return xstrdup("");
if (path[pos-1] == '.')
return DFSubstring(path,pos,len);
pos--;
}
}
char *DFPathWithoutExtension(const char *path)
{
size_t len = strlen(path);
if ((len > 0) && (path[len-1] == '/'))
len--;
if (!strcmp(path,"/"))
return xstrdup("/");;
size_t pos = len;
while (1) {
if (pos == 0)
return DFSubstring(path,0,len);
if (path[pos-1] == '/')
return DFSubstring(path,0,len);
if (path[pos-1] == '.')
return DFSubstring(path,0,pos-1);
pos--;
}
}
char *DFPathNormalize(const char *path)
{
size_t len = strlen(path);
char *result = (char *)xmalloc(len+1);
size_t outpos = 0;
for (size_t pos = 0; pos < len; pos++) {
if ((path[pos] != '/') || (pos == 0) || (path[pos-1] != '/'))
result[outpos++] = path[pos];
}
result[outpos] = '\0';
return result;
}
char *DFPathResolveAbsolute(const char *rawBase, const char *relative)
{
if (relative[0] == '/')
return xstrdup(relative);
char *base;
if (rawBase[0] == '/') {
base = xstrdup(rawBase);
}
else {
char cwd[4096];
getcwd(cwd,4096);
base = DFFormatString("%s/%s",cwd,rawBase);
}
char *baseDirectory = DFStringHasSuffix(base,"/") ? xstrdup(base) : DFPathDirName(base);
DFBuffer *path = DFBufferNew();
DFBufferAppendString(path,baseDirectory);
const char **relComponents = DFStringSplit(relative,"/",0);
for (size_t i = 0; relComponents[i]; i++) {
const char *adjust = relComponents[i];
if (!strcmp(adjust,".") || !strcmp(adjust,"")) {
// Do nothing
}
else if (!strcmp(adjust,"..")) {
// Remove last component from path
while ((path->len > 0) && (path->data[path->len-1] != '/'))
path->len--;
if (path->len > 0) {
path->data[path->len-1] = '\0';
}
}
else {
// Add new component to path
DFBufferAppendChar(path,'/');
DFBufferAppendString(path,adjust);
}
}
if (path->len == 0)
DFBufferAppendChar(path,'/');
char *result = DFPathNormalize(path->data);
free(relComponents);
free(base);
free(baseDirectory);
DFBufferRelease(path);
return result;
}
static int numFromHex(uint32_t ch)
{
if ((ch >= '0') && (ch <= '9'))
return ch - '0';
else if ((ch >= 'a') && (ch <= 'f'))
return 0xA + ch - 'a';
else if ((ch >= 'A') && (ch <= 'F'))
return 0xA + ch - 'A';
else
return -1;
}
char *DFRemovePercentEncoding(const char *encoded)
{
size_t inlen = strlen(encoded);
char *output = (char *)xmalloc(inlen+1);
size_t outpos = 0;
for (size_t inpos = 0; inpos < inlen; inpos++) {
char ch = encoded[inpos];
int hi;
int lo;
if ((ch == '%') &&
(inpos+2 < inlen) &&
((hi = numFromHex(encoded[inpos+1])) >= 0) &&
((lo = numFromHex(encoded[inpos+2])) >= 0)) {
((unsigned char *)output)[outpos++] = (hi << 4) | lo;
inpos += 2;
}
else {
output[outpos++] = ch;
}
}
assert(outpos <= inlen);
output[outpos] = 0;
return output;
}