Document which supported feature requires which operations. Document when stat fields need to be valid.
diff --git a/lib/zip_source_file.h b/lib/zip_source_file.h
index 07e6cfe..479686d 100644
--- a/lib/zip_source_file.h
+++ b/lib/zip_source_file.h
@@ -33,9 +33,9 @@
 
 struct zip_source_file_stat {
     zip_uint64_t size;    /* must be valid for regular files */
-    time_t mtime;
-    bool exists;
-    bool regular_file;
+    time_t mtime;         /* must always be valid, is initialized to current time */
+    bool exists;          /* must always be vaild */
+    bool regular_file;    /* must always be valid */
 };
 
 typedef struct zip_source_file_context zip_source_file_context_t;
@@ -63,6 +63,13 @@
     zip_source_file_operations_t *ops;
 };
 
+
+/* The following methods must be implemented to support each feature:
+   - close, read, seek, and stat must always be implemented.
+   - To support specifying the file by name, open, and strupd must be implemented.
+   - For write support, the file must be specified by name and close, commit_write, create_temp_output, remove, rollback_write, and tell must be implemented.
+   - create_temp_output_cloning is always optional. */
+
 struct zip_source_file_operations {
     void (*close)(zip_source_file_context_t *ctx);
     zip_int64_t (*commit_write)(zip_source_file_context_t *ctx);
diff --git a/lib/zip_source_file_common.c b/lib/zip_source_file_common.c
index c3a8fd4..8d0c599 100644
--- a/lib/zip_source_file_common.c
+++ b/lib/zip_source_file_common.c
@@ -60,10 +60,26 @@
 	zip_error_set(error, ZIP_ER_INVAL, 0);
 	return NULL;
     }
-
-    if (file == NULL && fname == NULL) {
-	zip_error_set(error, ZIP_ER_INVAL, 0);
-	return NULL;
+    
+    if (ops->close == NULL || ops->read == NULL || ops->seek == NULL || ops->stat == NULL) {
+        zip_error_set(error, ZIP_ER_INTERNAL, 0);
+        return NULL;
+    }
+    
+    if (ops->write != NULL && (ops->commit_write == NULL || ops->create_temp_output == NULL || ops->remove == NULL || ops->rollback_write == NULL || ops->tell == NULL)) {
+        zip_error_set(error, ZIP_ER_INTERNAL, 0);
+        return NULL;
+    }
+    
+    if (fname != NULL) {
+        if (ops->close == NULL || ops->open == NULL || ops->strdup == NULL) {
+            zip_error_set(error, ZIP_ER_INTERNAL, 0);
+            return NULL;
+        }
+    }
+    else if (file == NULL) {
+        zip_error_set(error, ZIP_ER_INVAL, 0);
+        return NULL;
     }
 
     if (len < 0) {
@@ -119,20 +135,18 @@
     zip_source_file_stat_init(&sb);
     stat_valid = ops->stat(ctx, &sb);
 
-    if (ctx->fname && !stat_valid) {
-	if (ctx->start == 0 && ctx->len == 0) {
-	    ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
-	}
+    if (ctx->fname && !stat_valid && ctx->start == 0 && ctx->len == 0 && ops->write != NULL) {
+        ctx->supports = ZIP_SOURCE_SUPPORTS_WRITABLE;
     }
 
     if (!stat_valid) {
-	zip_error_set(&ctx->stat_error, ZIP_ER_READ, errno);
+        zip_error_set(&ctx->stat_error, ZIP_ER_READ, errno);
     }
     else {
-	if ((ctx->st.valid & ZIP_STAT_MTIME) == 0) {
-	    ctx->st.mtime = sb.mtime;
+        if ((ctx->st.valid & ZIP_STAT_MTIME) == 0) {
+            ctx->st.mtime = sb.mtime;
 	    ctx->st.valid |= ZIP_STAT_MTIME;
-	}
+        }
 	if (sb.regular_file) {
 	    ctx->supports = ZIP_SOURCE_SUPPORTS_SEEKABLE;
 
@@ -248,7 +262,7 @@
 	    }
 	}
 
-	if (ctx->start > 0) {
+	if (ctx->start > 0) { // TODO: rewind on re-open
 	    if (ctx->ops->seek(ctx, ctx->f, (zip_int64_t)ctx->start, SEEK_SET) == false) {
 		/* TODO: skip by reading */
 		return -1;