/* Portable PDF attachment operations.  Embedded payloads are treated as
 * opaque bytes.  The runtime validates only the PDF container/stream; it
 * never executes or interprets the attached file format. */
typedef struct {
    char *name,*description,*mime,*relationship,*creation,*modified;
    int filespec_index,stream_index,annotation_index,page_number,is_annotation;
    size_t stored_size,decoded_size;
    int readable;
    char status[40],error_code[80],error_message[256],checksum[80],checksum_status[24];
} B2026PdfAttachment;
typedef struct { B2026PdfAttachment *items; int count,capacity; } B2026PdfAttachmentList;

static char *b2026_pdf_attachment_safe_name(const char *name,int index);
static char *b2026_pdf_attachment_join(const char *dir,const char *name);
static char *b2026_pdf_attachment_choose_path(const char *path,const char *policy);
static int b2026_pdf_attachment_write(const char *path,const unsigned char *data,size_t n);

static void b2026_pdf_attachment_item_free(B2026PdfAttachment *a){if(a==NULL)return;free(a->name);free(a->description);free(a->mime);free(a->relationship);free(a->creation);free(a->modified);memset(a,0,sizeof(*a));}
static void b2026_pdf_attachment_list_free(B2026PdfAttachmentList *l){int i;if(l==NULL)return;for(i=0;i<l->count;++i)b2026_pdf_attachment_item_free(&l->items[i]);free(l->items);memset(l,0,sizeof(*l));}
static int b2026_pdf_attachment_push(B2026PdfAttachmentList *l,const char *name,int fs,int stream,int annot,int page,int is_annot){B2026PdfAttachment *a;if(l->count==l->capacity){int cap=l->capacity==0?8:l->capacity*2;B2026PdfAttachment *p;if(cap<l->capacity||cap>1000000)return 0;p=(B2026PdfAttachment*)realloc(l->items,(size_t)cap*sizeof(*p));if(p==NULL)return 0;l->items=p;l->capacity=cap;}a=&l->items[l->count++];memset(a,0,sizeof(*a));a->name=b2026_strdup_simple(name!=NULL&&*name!='\0'?name:"attachment");a->filespec_index=fs;a->stream_index=stream;a->annotation_index=annot;a->page_number=page;a->is_annotation=is_annot;snprintf(a->status,sizeof(a->status),"NOT_CHECKED");snprintf(a->checksum_status,sizeof(a->checksum_status),"NOT_CHECKED");return a->name!=NULL;}

static int b2026_pdf_attachment_error(int code,const char *path,const char *detail){char message[4096];snprintf(message,sizeof(message),"PDF.ATTACHMENT failed: path=%s: %s",path!=NULL?path:"",detail!=NULL?detail:"unknown error / 不明なエラー");basic_set_error_logged(code,message,"PDF.ATTACHMENT",current_source_path,current_line,"PDF");b2026_try_append_log_line("__PDF_ERROR.log",message);return 0;}
static const char *b2026_pdf_attachment_error_string(int code,const char *path,const char *detail){(void)b2026_pdf_attachment_error(code,path,detail);return runtime_temp_string_copy("");}

static char *b2026_pdf_attachment_name_value(B2026PdfDocument *doc,const unsigned char *s,size_t n){size_t p=b2026_pdf_tf_space(s,n,0U);return b2026_pdf_parse_string_value(doc,s,n,p,0);}
static int b2026_pdf_attachment_ref_value(B2026PdfDocument *doc,const unsigned char *s,size_t n){int a,g;if(!b2026_pdf_tf_ref(s,n,&a,&g))return -1;return b2026_pdf_object_lookup(doc,a,g);}
static int b2026_pdf_attachment_resolve_key(B2026PdfDocument *doc,int oi,const char *key,int *ref_index,const unsigned char **value,size_t *length){B2026PdfKeyRange r;B2026PdfObject *o;if(oi<0||oi>=doc->object_count)return 0;o=&doc->objects[oi];if(!b2026_pdf_tf_find_key(o->body,o->body_length,key,&r)||!r.found)return 0;*ref_index=b2026_pdf_attachment_ref_value(doc,o->body+r.value_start,r.value_end-r.value_start);if(*ref_index>=0){*value=doc->objects[*ref_index].body;*length=doc->objects[*ref_index].body_length;}else{*value=o->body+r.value_start;*length=r.value_end-r.value_start;}return 1;}

static int b2026_pdf_attachment_filespec_stream(B2026PdfDocument *doc,int fs){B2026PdfObject *o;int n,g;if(fs<0||fs>=doc->object_count)return -1;o=&doc->objects[fs];if(b2026_pdf_named_ref(o->body,o->body_length,"/UF",&n,&g)||(b2026_pdf_named_ref(o->body,o->body_length,"/F",&n,&g)))return b2026_pdf_object_lookup(doc,n,g);return -1;}
static char *b2026_pdf_attachment_filespec_name(B2026PdfDocument *doc,int fs,const char *fallback){char *s=NULL;if(fs>=0&&fs<doc->object_count){B2026PdfObject *o=&doc->objects[fs];s=b2026_pdf_dict_string(doc,o->body,o->body_length,"/UF");if(s==NULL)s=b2026_pdf_dict_string(doc,o->body,o->body_length,"/F");}if(s==NULL)s=b2026_strdup_simple(fallback!=NULL?fallback:"attachment");return s;}

static int b2026_pdf_attachment_collect_names_array(B2026PdfDocument *doc,const unsigned char *body,size_t n,B2026PdfAttachmentList *list){B2026PdfKeyRange r;size_t p,end;if(!b2026_pdf_tf_find_key(body,n,"/Names",&r))return 0;if(!r.found)return 1;p=b2026_pdf_tf_space(body,n,r.value_start);end=r.value_end;if(p>=end||body[p]!='[')return 0;++p;while((p=b2026_pdf_tf_space(body,end,p))<end&&body[p]!=']'){size_t ke=b2026_pdf_tf_value_end(body,end,p,0),ve;char *tree_name,*fs_name;int fs,stream;if(ke<=p||ke>end)return 0;tree_name=b2026_pdf_attachment_name_value(doc,body+p,ke-p);p=b2026_pdf_tf_space(body,end,ke);ve=b2026_pdf_tf_value_end(body,end,p,0);if(ve<=p||ve>end){free(tree_name);return 0;}fs=b2026_pdf_attachment_ref_value(doc,body+p,ve-p);if(fs<0){free(tree_name);return 0;}stream=b2026_pdf_attachment_filespec_stream(doc,fs);fs_name=b2026_pdf_attachment_filespec_name(doc,fs,tree_name);if(!b2026_pdf_attachment_push(list,fs_name,fs,stream,-1,0,0)){free(tree_name);free(fs_name);return 0;}free(tree_name);free(fs_name);p=ve;}return 1;}

static int b2026_pdf_attachment_collect_name_tree(B2026PdfDocument *doc,const unsigned char *root,size_t root_n,int root_index,B2026PdfAttachmentList *list){int *stack=NULL,top=0,capacity=0;unsigned char *visited=NULL;const unsigned char *body=root;size_t n=root_n;if(root_index>=0){capacity=16;stack=(int*)malloc((size_t)capacity*sizeof(*stack));visited=(unsigned char*)calloc((size_t)doc->object_count,1U);if(stack==NULL||visited==NULL)goto fail;stack[top++]=root_index;}else if(!b2026_pdf_attachment_collect_names_array(doc,body,n,list))goto fail;while(top>0){B2026PdfKeyRange kids;size_t p,end;int oi=stack[--top];if(oi<0||oi>=doc->object_count||visited[oi])continue;visited[oi]=1U;body=doc->objects[oi].body;n=doc->objects[oi].body_length;if(!b2026_pdf_attachment_collect_names_array(doc,body,n,list))goto fail;if(!b2026_pdf_tf_find_key(body,n,"/Kids",&kids))goto fail;if(!kids.found)continue;p=kids.value_start;end=kids.value_end;while(p<end){size_t ve;int child;p=b2026_pdf_tf_space(body,end,p);if(p>=end)break;if(body[p]=='['||body[p]==']'){++p;continue;}ve=b2026_pdf_tf_value_end(body,end,p,0);if(ve<=p||ve>end)goto fail;child=b2026_pdf_attachment_ref_value(doc,body+p,ve-p);if(child>=0&&!visited[child]){if(top==capacity){int nc=capacity*2;int *q;if(nc>doc->object_count+1)nc=doc->object_count+1;if(nc<=capacity)goto fail;q=(int*)realloc(stack,(size_t)nc*sizeof(*q));if(q==NULL)goto fail;stack=q;capacity=nc;}stack[top++]=child;}p=ve;}}
    free(stack);free(visited);return 1;
fail:free(stack);free(visited);return 0;
}

static int b2026_pdf_attachment_collect_document(B2026PdfDocument *doc,B2026PdfAttachmentList *list){const unsigned char *names,*embedded;size_t names_n,embedded_n;int names_ref=-1,embedded_ref=-1;if(doc->root_object<0)return 0;if(!b2026_pdf_attachment_resolve_key(doc,doc->root_object,"/Names",&names_ref,&names,&names_n))return 1;{B2026PdfKeyRange r;if(!b2026_pdf_tf_find_key(names,names_n,"/EmbeddedFiles",&r))return 0;if(!r.found)return 1;embedded_ref=b2026_pdf_attachment_ref_value(doc,names+r.value_start,r.value_end-r.value_start);if(embedded_ref>=0){embedded=doc->objects[embedded_ref].body;embedded_n=doc->objects[embedded_ref].body_length;}else{embedded=names+r.value_start;embedded_n=r.value_end-r.value_start;}}return b2026_pdf_attachment_collect_name_tree(doc,embedded,embedded_n,embedded_ref,list);}

static int b2026_pdf_attachment_collect_annotation_array(B2026PdfDocument *doc,const unsigned char *body,size_t n,int page,B2026PdfAttachmentList *list){size_t p=0U;while(p<n){size_t ve;int ai,fs,stream,nr,gr;B2026PdfObject *a;char *name;p=b2026_pdf_tf_space(body,n,p);if(p>=n)break;if(body[p]=='['||body[p]==']'){++p;continue;}ve=b2026_pdf_tf_value_end(body,n,p,0);if(ve<=p||ve>n)return 0;ai=b2026_pdf_attachment_ref_value(doc,body+p,ve-p);if(ai>=0){a=&doc->objects[ai];if(b2026_pdf_dict_name_is(a->body,a->body_length,"/Subtype","FileAttachment")&&b2026_pdf_named_ref(a->body,a->body_length,"/FS",&nr,&gr)&&(fs=b2026_pdf_object_lookup(doc,nr,gr))>=0){stream=b2026_pdf_attachment_filespec_stream(doc,fs);name=b2026_pdf_attachment_filespec_name(doc,fs,"attachment");if(!b2026_pdf_attachment_push(list,name,fs,stream,ai,page,1)){free(name);return 0;}free(name);}}p=ve;}return 1;}
static int b2026_pdf_attachment_collect_annotations(B2026PdfDocument *doc,B2026PdfAttachmentList *list){int i;for(i=0;i<doc->page_count;++i){int page=doc->page_objects[i],ref;const unsigned char *v;size_t n;if(b2026_pdf_attachment_resolve_key(doc,page,"/Annots",&ref,&v,&n)&&!b2026_pdf_attachment_collect_annotation_array(doc,v,n,i+1,list))return 0;}return 1;}
static int b2026_pdf_attachment_collect(B2026PdfDocument *doc,B2026PdfAttachmentList *list){memset(list,0,sizeof(*list));return b2026_pdf_attachment_collect_document(doc,list)&&b2026_pdf_attachment_collect_annotations(doc,list);}

/* Small C99 MD5 implementation, used only to compare the optional PDF
 * EmbeddedFile /CheckSum value.  It is not presented as a security hash. */
typedef struct{uint32_t h[4];uint64_t bytes;unsigned char block[64];size_t used;}B2026PdfMd5;
static uint32_t b2026_pdf_md5_rot(uint32_t x,unsigned int n){return(x<<n)|(x>>(32U-n));}
static void b2026_pdf_md5_block(B2026PdfMd5*c,const unsigned char*p){static const uint32_t k[64]={0xd76aa478U,0xe8c7b756U,0x242070dbU,0xc1bdceeeU,0xf57c0fafU,0x4787c62aU,0xa8304613U,0xfd469501U,0x698098d8U,0x8b44f7afU,0xffff5bb1U,0x895cd7beU,0x6b901122U,0xfd987193U,0xa679438eU,0x49b40821U,0xf61e2562U,0xc040b340U,0x265e5a51U,0xe9b6c7aaU,0xd62f105dU,0x02441453U,0xd8a1e681U,0xe7d3fbc8U,0x21e1cde6U,0xc33707d6U,0xf4d50d87U,0x455a14edU,0xa9e3e905U,0xfcefa3f8U,0x676f02d9U,0x8d2a4c8aU,0xfffa3942U,0x8771f681U,0x6d9d6122U,0xfde5380cU,0xa4beea44U,0x4bdecfa9U,0xf6bb4b60U,0xbebfbc70U,0x289b7ec6U,0xeaa127faU,0xd4ef3085U,0x04881d05U,0xd9d4d039U,0xe6db99e5U,0x1fa27cf8U,0xc4ac5665U,0xf4292244U,0x432aff97U,0xab9423a7U,0xfc93a039U,0x655b59c3U,0x8f0ccc92U,0xffeff47dU,0x85845dd1U,0x6fa87e4fU,0xfe2ce6e0U,0xa3014314U,0x4e0811a1U,0xf7537e82U,0xbd3af235U,0x2ad7d2bbU,0xeb86d391U};static const unsigned char s[64]={7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21};uint32_t m[16],a=c->h[0],b=c->h[1],cc=c->h[2],d=c->h[3];int i;for(i=0;i<16;++i)m[i]=(uint32_t)p[i*4]|((uint32_t)p[i*4+1]<<8)|((uint32_t)p[i*4+2]<<16)|((uint32_t)p[i*4+3]<<24);for(i=0;i<64;++i){uint32_t f,g,t;if(i<16){f=(b&cc)|((~b)&d);g=(uint32_t)i;}else if(i<32){f=(d&b)|((~d)&cc);g=(uint32_t)((5*i+1)%16);}else if(i<48){f=b^cc^d;g=(uint32_t)((3*i+5)%16);}else{f=cc^(b|(~d));g=(uint32_t)((7*i)%16);}t=d;d=cc;cc=b;b=b+b2026_pdf_md5_rot(a+f+k[i]+m[g],s[i]);a=t;}c->h[0]+=a;c->h[1]+=b;c->h[2]+=cc;c->h[3]+=d;}
static void b2026_pdf_md5_init(B2026PdfMd5*c){memset(c,0,sizeof(*c));c->h[0]=0x67452301U;c->h[1]=0xefcdab89U;c->h[2]=0x98badcfeU;c->h[3]=0x10325476U;}
static void b2026_pdf_md5_update(B2026PdfMd5*c,const unsigned char*p,size_t n){c->bytes+=(uint64_t)n;while(n>0U){size_t take=64U-c->used;if(take>n)take=n;memcpy(c->block+c->used,p,take);c->used+=take;p+=take;n-=take;if(c->used==64U){b2026_pdf_md5_block(c,c->block);c->used=0U;}}}
static void b2026_pdf_md5_final(B2026PdfMd5*c,unsigned char out[16]){uint64_t bits=c->bytes*8U;size_t i;c->block[c->used++]=0x80U;if(c->used>56U){while(c->used<64U)c->block[c->used++]=0;b2026_pdf_md5_block(c,c->block);c->used=0;}while(c->used<56U)c->block[c->used++]=0;for(i=0;i<8U;++i)c->block[c->used++]=(unsigned char)(bits>>(8U*i));b2026_pdf_md5_block(c,c->block);for(i=0;i<16U;++i)out[i]=(unsigned char)(c->h[i/4]>>(8U*(i%4)));}

/* Binary-safe checksum parser, separate from the text buffer helper. */
static int b2026_pdf_attachment_checksum_bytes(const unsigned char *body,size_t n,unsigned char out[16]){const unsigned char *found=b2026_pdf_memfind(body,n,"/CheckSum",9U);size_t p,count=0;if(found==NULL)return 0;p=b2026_pdf_tf_space(body,n,(size_t)(found-body)+9U);if(p>=n)return -1;if(body[p]=='<'){int high=-1;++p;while(p<n&&body[p]!='>'){int v;unsigned char c=body[p++];if(isspace(c))continue;v=b2026_pdf_hex_value(c);if(v<0)return -1;if(high<0)high=v;else{if(count>=16U)return -1;out[count++]=(unsigned char)((high<<4)|v);high=-1;}}if(p>=n||body[p]!='>')return -1;if(high>=0){if(count>=16U)return -1;out[count++]=(unsigned char)(high<<4);}}else if(body[p]=='('){++p;while(p<n&&body[p]!=')'){unsigned char c=body[p++];if(c=='\\'&&p<n)c=body[p++];if(count>=16U)return -1;out[count++]=c;}if(p>=n||body[p]!=')')return -1;}else return -1;return count==16U?1:-1;}

static int b2026_pdf_attachment_stream_decode(B2026PdfDocument *doc,B2026PdfAttachment *a,unsigned char **data,size_t *data_n){B2026PdfObject *o;const unsigned char *stream,*payload,*endstream;size_t stored,raw_span,limit,dict_n;char filters[8][24];int count=0,opaque=0,rc,declared;unsigned char *decoded=NULL;size_t decoded_n=0U;unsigned char want[16],got[16];B2026PdfMd5 md5;int check;
    *data=NULL;*data_n=0U;a->readable=0;a->stored_size=a->decoded_size=0U;snprintf(a->status,sizeof(a->status),"DAMAGED");snprintf(a->error_code,sizeof(a->error_code),"ATTACHMENT_STREAM_MISSING");snprintf(a->error_message,sizeof(a->error_message),"embedded-file stream is missing / 添付ストリームがありません");snprintf(a->checksum_status,sizeof(a->checksum_status),"NOT_PRESENT");if(a->stream_index<0||a->stream_index>=doc->object_count)return 0;o=&doc->objects[a->stream_index];stream=b2026_pdf_memfind(o->body,o->body_length,"stream",6U);if(stream==NULL)return 0;payload=stream+6U;if(payload<o->body+o->body_length&&*payload=='\r')++payload;if(payload<o->body+o->body_length&&*payload=='\n')++payload;endstream=b2026_pdf_memfind(payload,o->body_length-(size_t)(payload-o->body),"endstream",9U);if(endstream==NULL){snprintf(a->status,sizeof(a->status),"TRUNCATED");snprintf(a->error_code,sizeof(a->error_code),"ATTACHMENT_TRUNCATED");snprintf(a->error_message,sizeof(a->error_message),"embedded-file stream is truncated / 添付ストリームが途中で切れています");return 0;}raw_span=(size_t)(endstream-payload);dict_n=(size_t)(stream-o->body);if(b2026_pdf_dict_integer(o->body,dict_n,"/Length",&declared)&&declared>=0){if((size_t)declared>raw_span){snprintf(a->status,sizeof(a->status),"TRUNCATED");snprintf(a->error_code,sizeof(a->error_code),"ATTACHMENT_LENGTH_MISMATCH");snprintf(a->error_message,sizeof(a->error_message),"embedded-file Length exceeds stored data / 添付ストリームのLengthが実データを超えています");return 0;}stored=(size_t)declared;}else{stored=raw_span;while(stored>0U&&(payload[stored-1U]=='\r'||payload[stored-1U]=='\n'))--stored;}a->stored_size=stored;if(!b2026_pdf_sc_filter_list(o->body,dict_n,filters,&count,&opaque)||opaque){snprintf(a->status,sizeof(a->status),"UNSUPPORTED_FILTER");snprintf(a->error_code,sizeof(a->error_code),"ATTACHMENT_UNSUPPORTED_FILTER");snprintf(a->error_message,sizeof(a->error_message),"unsupported attachment stream filter / 未対応の添付圧縮方式です");return 0;}limit=(size_t)B2026_PDF_EDIT_MAX_BYTES-1U;if(stored<=SIZE_MAX/1000U){size_t ratio=stored*1000U;if(ratio<67108864U)ratio=67108864U;if(ratio<limit)limit=ratio;}rc=b2026_pdf_sc_decode_filters(payload,stored,filters,count,limit,&decoded,&decoded_n);if(rc<=0){snprintf(a->status,sizeof(a->status),rc<0?"SIZE_LIMIT":"FILTER_ERROR");snprintf(a->error_code,sizeof(a->error_code),rc<0?"ATTACHMENT_SIZE_LIMIT":"ATTACHMENT_FILTER_ERROR");snprintf(a->error_message,sizeof(a->error_message),rc<0?"attachment exceeds the safe expansion limit / 添付データが安全な展開上限を超えました":"attachment stream could not be decoded / 添付ストリームを展開できません");return 0;}if(decoded_n>67108864U&&stored>0U&&decoded_n/stored>1000U){free(decoded);snprintf(a->status,sizeof(a->status),"SUSPICIOUS_COMPRESSION");snprintf(a->error_code,sizeof(a->error_code),"ATTACHMENT_SUSPICIOUS_COMPRESSION");snprintf(a->error_message,sizeof(a->error_message),"suspicious attachment compression ratio / 添付データの圧縮率が異常です");return 0;}a->decoded_size=decoded_n;check=b2026_pdf_attachment_checksum_bytes(o->body,dict_n,want);if(check>0){static const char hex[]="0123456789abcdef";int i;b2026_pdf_md5_init(&md5);b2026_pdf_md5_update(&md5,decoded,decoded_n);b2026_pdf_md5_final(&md5,got);for(i=0;i<16;++i){a->checksum[i*2]=hex[want[i]>>4];a->checksum[i*2+1]=hex[want[i]&15];}a->checksum[32]='\0';if(memcmp(want,got,16U)!=0){free(decoded);snprintf(a->checksum_status,sizeof(a->checksum_status),"MISMATCH");snprintf(a->status,sizeof(a->status),"CHECKSUM_MISMATCH");snprintf(a->error_code,sizeof(a->error_code),"ATTACHMENT_CHECKSUM_MISMATCH");snprintf(a->error_message,sizeof(a->error_message),"attachment checksum does not match / 添付ファイルのチェックサムが一致しません");return 0;}snprintf(a->checksum_status,sizeof(a->checksum_status),"MATCH");}else if(check<0)snprintf(a->checksum_status,sizeof(a->checksum_status),"NOT_CHECKED");a->readable=1;snprintf(a->status,sizeof(a->status),"OK");a->error_code[0]=a->error_message[0]='\0';*data=decoded;*data_n=decoded_n;return 1;}

static char *b2026_pdf_attachment_decode_name(char *name){char *src,*dst;if(name==NULL)return NULL;src=dst=name;while(*src!='\0'){if(src[0]=='#'&&isxdigit((unsigned char)src[1])&&isxdigit((unsigned char)src[2])){*dst++=(char)((b2026_pdf_hex_value((unsigned char)src[1])<<4)|b2026_pdf_hex_value((unsigned char)src[2]));src+=3;}else *dst++=*src++;}*dst='\0';return name;}
static int b2026_pdf_attachment_policy_ok(const char *value){return value!=NULL&&(b2026_strcasecmp(value,"RENAME")==0||b2026_strcasecmp(value,"REPLACE")==0||b2026_strcasecmp(value,"ERROR")==0);}
static int b2026_pdf_attachment_relationship_ok(const char *value){static const char *allowed[]={"UNSPECIFIED","SOURCE","DATA","ALTERNATIVE","SUPPLEMENT","ENCRYPTEDPAYLOAD","FORMDATA","SCHEMA"};size_t i;if(value==NULL)return 0;for(i=0;i<sizeof(allowed)/sizeof(allowed[0]);++i)if(b2026_strcasecmp(value,allowed[i])==0)return 1;return 0;}
static void b2026_pdf_attachment_metadata(B2026PdfDocument *doc,B2026PdfAttachment *a){B2026PdfObject *fs,*st;if(a->filespec_index>=0&&a->filespec_index<doc->object_count){fs=&doc->objects[a->filespec_index];a->description=b2026_pdf_dict_string(doc,fs->body,fs->body_length,"/Desc");a->relationship=b2026_pdf_dict_name(fs->body,fs->body_length,"/AFRelationship");}if(a->stream_index>=0&&a->stream_index<doc->object_count){st=&doc->objects[a->stream_index];a->creation=b2026_pdf_dict_string(doc,st->body,st->body_length,"/CreationDate");a->modified=b2026_pdf_dict_string(doc,st->body,st->body_length,"/ModDate");a->mime=b2026_pdf_attachment_decode_name(b2026_pdf_dict_name(st->body,st->body_length,"/Subtype"));}}

static int b2026_pdf_attachment_array_commit(const char *target,char **values,int count){Frame *frame;ArrayVariable *old,*result;int i;size_t slots;if(target==NULL)return 1;frame=get_array_frame(target);if(frame==NULL)return 0;result=(ArrayVariable*)calloc(1,sizeof(*result));if(result==NULL)return 0;slots=(size_t)count+(size_t)option_base;if(slots==0U)slots=1U;if(slots>SIZE_MAX/sizeof(B2026_Var)){free(result);return 0;}result->data=(B2026_Var*)calloc(slots,sizeof(B2026_Var));result->ref_count_ptr=(int*)malloc(sizeof(int));if(result->data==NULL||result->ref_count_ptr==NULL){free(result->data);free(result->ref_count_ptr);free(result);return 0;}*result->ref_count_ptr=1;strncpy(result->name,target,sizeof(result->name)-1U);result->size=option_base+count-1;result->read_count=count;basic_reset_canvas_pen_state(result);for(i=0;i<count;++i){result->data[option_base+i].type=1;result->data[option_base+i].data.ptr=b2026_strdup_simple(values[i]);if(result->data[option_base+i].data.ptr==NULL){release_array_storage(result);free(result);return 0;}}old=find_local_array(frame,target);if(old!=NULL){ArrayVariable *next=old->next;release_array_storage(old);*old=*result;old->next=next;free(result);}else{result->next=frame->arrays;frame->arrays=result;}return 1;}
static int b2026_pdf_attachment_array(const char *target,const char *operation,int argc,const B2026PdfArg *args){B2026PdfDocument doc;B2026PdfAttachmentList list;char **values=NULL;const char *path;int rc,i,count=0,ok=0;memset(&doc,0,sizeof(doc));memset(&list,0,sizeof(list));if(target!=NULL&&!b2026_pdf_attachment_array_commit(target,NULL,0)){b2026_pdf_attachment_error(4269,"","out of memory while initializing the result array / 結果配列の初期化に必要なメモリを確保できません");return 0;}if((strcmp(operation,"ATTACHMENTLIST")==0&&argc!=1)||(strcmp(operation,"ATTACHMENTEXTRACTALL")==0&&(argc<2||argc>3))||args[0].kind!=1||args[0].text==NULL){b2026_pdf_attachment_error(4260,"","invalid attachment array operation parameters / 添付配列操作の引数が正しくありません");return 0;}path=args[0].text;rc=b2026_pdf_load(&doc,path,0);if(rc<=0){b2026_pdf_attachment_error(4261,path,b2026_pdf_load_error(rc));goto done;}if(!b2026_pdf_attachment_collect(&doc,&list)){b2026_pdf_attachment_error(4262,path,"attachment name tree is damaged / 添付ファイルの名前ツリーが破損しています");goto done;}values=(char**)calloc((size_t)(list.count>0?list.count:1),sizeof(*values));if(values==NULL)goto memory_fail;if(strcmp(operation,"ATTACHMENTLIST")==0){for(i=0;i<list.count;++i){values[count]=b2026_strdup_simple(list.items[i].name);if(values[count++]==NULL)goto memory_fail;}}else{const char *dir=args[1].kind==1?args[1].text:NULL;HashVariable *h=NULL;const char *policy="RENAME";if(dir==NULL||*dir=='\0'||(argc==3&&!b2026_pdf_tf_hash(&args[2],&h))){b2026_pdf_attachment_error(4263,path,"EXTRACTATTACHMENTS requires PDF path, destination directory, and optional settings / 添付一括抽出の引数が正しくありません");goto done;}if(h!=NULL){const char *p=b2026_pdf_tf_text(h,"既存ファイル","existingFile");if(p!=NULL)policy=p;}for(i=0;i<list.count;++i){unsigned char *data=NULL;size_t n=0U;char *safe=NULL,*dest=NULL,*actual=NULL;if(!b2026_pdf_attachment_stream_decode(&doc,&list.items[i],&data,&n)){b2026_pdf_attachment_error(4264,path,list.items[i].error_message);continue;}safe=b2026_pdf_attachment_safe_name(list.items[i].name,i+1);dest=b2026_pdf_attachment_join(dir,safe);actual=b2026_pdf_attachment_choose_path(dest,policy);if(actual==NULL||!b2026_pdf_attachment_write(actual,data,n)){b2026_pdf_attachment_error(4265,dest!=NULL?dest:dir,"attachment could not be written safely / 添付ファイルを安全に保存できません");free(data);free(safe);free(dest);free(actual);continue;}values[count++]=actual;free(data);free(safe);free(dest);}}
    if(!b2026_pdf_attachment_array_commit(target,values,count))goto memory_fail;ok=1;goto done;
memory_fail:b2026_pdf_attachment_error(4269,path,"out of memory / メモリ不足です");
done:if(values!=NULL)for(i=0;i<count;++i)free(values[i]);free(values);b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return ok;}

static int b2026_pdf_attachment_select(B2026PdfAttachmentList *list,const B2026PdfArg *arg){int index,i;if(b2026_pdf_arg_integer(arg,&index))return index>=1&&index<=list->count?index-1:-1;if(arg->kind==1&&arg->text!=NULL){for(i=0;i<list->count;++i)if(strcmp(list->items[i].name,arg->text)==0)return i;}return -1;}
static void b2026_pdf_attachment_pair_number(const char*t,const char*j,const char*e,double v){hash_set_number(t,j,v);hash_set_number(t,e,v);}static void b2026_pdf_attachment_pair_string(const char*t,const char*j,const char*e,const char*v){hash_set_string(t,j,v!=NULL?v:"");hash_set_string(t,e,v!=NULL?v:"");}
static int b2026_pdf_attachment_info(const char *target,int argc,const B2026PdfArg *args){HashVariable *h;B2026PdfDocument doc;B2026PdfAttachmentList list;B2026PdfAttachment *a;unsigned char *data=NULL;size_t n=0U;int rc,index;memset(&doc,0,sizeof(doc));memset(&list,0,sizeof(list));if(target==NULL||(h=ensure_local_hash(get_hash_frame(target),target))==NULL)return 0;clear_hash_entries(h);if(argc!=2||args[0].kind!=1||args[0].text==NULL){b2026_pdf_attachment_error(4270,"","ATTACHMENTINFO requires PDF path and attachment index or name / 添付情報にはPDFパスと添付番号または名前が必要です");return 0;}rc=b2026_pdf_load(&doc,args[0].text,0);if(rc<=0){b2026_pdf_attachment_error(4271,args[0].text,b2026_pdf_load_error(rc));goto fail;}if(!b2026_pdf_attachment_collect(&doc,&list)){b2026_pdf_attachment_error(4272,args[0].text,"attachment name tree is damaged / 添付ファイルの名前ツリーが破損しています");goto fail;}index=b2026_pdf_attachment_select(&list,&args[1]);if(index<0){b2026_pdf_attachment_error(4273,args[0].text,"attachment was not found / 指定された添付ファイルがありません");goto fail;}a=&list.items[index];b2026_pdf_attachment_metadata(&doc,a);(void)b2026_pdf_attachment_stream_decode(&doc,a,&data,&n);free(data);b2026_pdf_attachment_pair_number(target,"正常","ok",a->readable?1.0:0.0);b2026_pdf_attachment_pair_number(target,"添付番号","index",(double)(index+1));b2026_pdf_attachment_pair_string(target,"状態","status",a->status);b2026_pdf_attachment_pair_number(target,"読取可能","readable",a->readable?1.0:0.0);b2026_pdf_attachment_pair_string(target,"ファイル名","filename",a->name);b2026_pdf_attachment_pair_string(target,"説明","description",a->description);b2026_pdf_attachment_pair_string(target,"MIMEタイプ","mimeType",a->mime);b2026_pdf_attachment_pair_number(target,"格納サイズ","storedSize",(double)a->stored_size);b2026_pdf_attachment_pair_number(target,"展開サイズ","decodedSize",(double)a->decoded_size);b2026_pdf_attachment_pair_string(target,"作成日時","creationDate",a->creation);b2026_pdf_attachment_pair_string(target,"更新日時","modDate",a->modified);b2026_pdf_attachment_pair_string(target,"チェックサム","checksum",a->checksum);b2026_pdf_attachment_pair_string(target,"チェックサム状態","checksumStatus",a->checksum_status);b2026_pdf_attachment_pair_string(target,"関連種別","relationship",a->relationship);b2026_pdf_attachment_pair_number(target,"注釈添付","annotationAttachment",a->is_annotation?1.0:0.0);b2026_pdf_attachment_pair_number(target,"ページ番号","page",a->is_annotation?(double)a->page_number:null_value());b2026_pdf_attachment_pair_string(target,"内容検証","contentValidation","NOT_VALIDATED");b2026_pdf_attachment_pair_string(target,"エラーコード","errorCode",a->error_code);b2026_pdf_attachment_pair_string(target,"エラー内容","errorMessage",a->error_message);if(!a->readable)b2026_pdf_attachment_error(4274,args[0].text,a->error_message);b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return 1;
fail:b2026_pdf_attachment_pair_number(target,"正常","ok",0.0);b2026_pdf_attachment_pair_string(target,"状態","status",rc==-2?"ENCRYPTED":"NOT_FOUND");b2026_pdf_attachment_pair_number(target,"読取可能","readable",0.0);b2026_pdf_attachment_pair_string(target,"内容検証","contentValidation","NOT_VALIDATED");b2026_pdf_attachment_pair_string(target,"エラーコード","errorCode",rc==-2?"ATTACHMENT_ENCRYPTED":"ATTACHMENT_NOT_FOUND");b2026_pdf_attachment_pair_string(target,"エラー内容","errorMessage",rc==-2?"encrypted/password-protected PDF is not supported / 暗号化・パスワード保護PDFには対応していません":"attachment was not found or attachment tree is damaged / 添付がないか添付ツリーが破損しています");b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return 0;}

static int b2026_pdf_attachment_path_exists(const char *p){return p!=NULL&&b2026_access(p,0)==0;}
static int b2026_pdf_attachment_is_link(const char *p){if(p==NULL)return 0;
#if defined(_WIN32)
    {wchar_t *w=b2026_utf8_path_to_wide(p);DWORD a;if(w==NULL)return 1;a=GetFileAttributesW(w);free(w);return a!=INVALID_FILE_ATTRIBUTES&&(a&FILE_ATTRIBUTE_REPARSE_POINT)!=0;}
#else
    {struct stat st;return lstat(p,&st)==0&&S_ISLNK(st.st_mode);}
#endif
}
static int b2026_pdf_attachment_reserved_name(const char *s){char stem[16];size_t i=0U;while(s[i]!='\0'&&s[i]!='.'&&i<sizeof(stem)-1U){stem[i]=(char)toupper((unsigned char)s[i]);++i;}stem[i]='\0';if(strcmp(stem,"CON")==0||strcmp(stem,"PRN")==0||strcmp(stem,"AUX")==0||strcmp(stem,"NUL")==0)return 1;if(strlen(stem)==4U&&(memcmp(stem,"COM",3U)==0||memcmp(stem,"LPT",3U)==0)&&stem[3]>='1'&&stem[3]<='9')return 1;return 0;}
static char *b2026_pdf_attachment_safe_name(const char *name,int index){const char *base=name,*p;char out[260];size_t used=0U;if(base==NULL)base="";for(p=base;*p!='\0';++p)if(*p=='/'||*p=='\\')base=p+1;for(p=base;*p!='\0'&&used<240U;++p){unsigned char c=(unsigned char)*p;if(c<32U||c==127U||c=='/'||c=='\\'||c==':')out[used++]='_';else out[used++]=(char)c;}while(used>0U&&(out[used-1U]==' '||out[used-1U]=='.'))--used;out[used]='\0';if(used==0U||strcmp(out,".")==0||strcmp(out,"..")==0)snprintf(out,sizeof(out),"attachment_%03d",index);else if(b2026_pdf_attachment_reserved_name(out)){memmove(out+1,out,used+1U);out[0]='_';}return b2026_strdup_simple(out);}
static char *b2026_pdf_attachment_join(const char *dir,const char *name){size_t a=strlen(dir),b=strlen(name);int sep=a>0U&&dir[a-1U]!='/'&&dir[a-1U]!='\\';char *r;if(a>SIZE_MAX-b-(size_t)sep-1U)return NULL;r=(char*)malloc(a+b+(size_t)sep+1U);if(r==NULL)return NULL;memcpy(r,dir,a);if(sep)r[a++]='/';memcpy(r+a,name,b+1U);return r;}
static char *b2026_pdf_attachment_choose_path(const char *path,const char *policy){int i;if(path==NULL||!b2026_pdf_attachment_policy_ok(policy))return NULL;if(!b2026_pdf_attachment_path_exists(path))return b2026_strdup_simple(path);if(b2026_pdf_attachment_is_link(path))return NULL;if(b2026_strcasecmp(policy,"REPLACE")==0)return b2026_strdup_simple(path);if(b2026_strcasecmp(policy,"ERROR")==0)return NULL;for(i=2;i<1000000;++i){const char *slash=strrchr(path,'/'),*back=strrchr(path,'\\'),*dot;size_t stem,n;char *r;if(back!=NULL&&(slash==NULL||back>slash))slash=back;dot=strrchr(path,'.');if(dot==NULL||(slash!=NULL&&dot<slash))dot=path+strlen(path);stem=(size_t)(dot-path);n=stem+32U+strlen(dot);r=(char*)malloc(n);if(r==NULL)return NULL;snprintf(r,n,"%.*s_%d%s",(int)stem,path,i,dot);if(!b2026_pdf_attachment_path_exists(r))return r;free(r);}return NULL;}
static int b2026_pdf_attachment_write(const char *path,const unsigned char *data,size_t n){char detail[512],*stage;FILE *fp;int ok=0;if(path==NULL||b2026_pdf_attachment_is_link(path)||!saveimage_ensure_directories_safe(path,detail,sizeof(detail)))return 0;stage=b2026_atomic_temp_path(path);if(stage==NULL)return 0;fp=b2026_fopen(stage,"wb");if(fp!=NULL){b2026_io_hard_guard_begin("PDF.ATTACHMENT",path,"WRITE");if((n==0U||fwrite(data,1U,n,fp)==n)&&fflush(fp)==0&&!ferror(fp))ok=1;b2026_io_hard_guard_end();if(fclose(fp)!=0)ok=0;}if(ok)ok=b2026_atomic_commit_file(stage,path);if(!ok)b2026_atomic_discard_file(stage);free(stage);return ok;}

static char *b2026_pdf_attachment_pdf_string(const char *text){B2026PdfTextBuffer b;const unsigned char *p=(const unsigned char*)(text!=NULL?text:"");memset(&b,0,sizeof(b));if(!b2026_pdf_text_append_bytes(&b,"<FEFF",5U))goto fail;while(*p){int span=utf8_char_span(p);unsigned int cp=utf8_decode_codepoint(p,span);char hex[12];if(span<=0)span=1;if(cp<=0xFFFFU){if(cp>=0xD800U&&cp<=0xDFFFU)cp=0xFFFDU;snprintf(hex,sizeof(hex),"%04X",cp);if(!b2026_pdf_text_append_bytes(&b,hex,4U))goto fail;}else{unsigned int v=cp-0x10000U,hi=0xD800U+(v>>10),lo=0xDC00U+(v&1023U);snprintf(hex,sizeof(hex),"%04X%04X",hi,lo);if(!b2026_pdf_text_append_bytes(&b,hex,8U))goto fail;}p+=(size_t)span;}if(!b2026_pdf_text_append_char(&b,'>'))goto fail;return b2026_pdf_text_take(&b);fail:free(b.data);return NULL;}
static char *b2026_pdf_attachment_pdf_name(const char *text){static const char hex[]="0123456789ABCDEF";B2026PdfTextBuffer b;const unsigned char *p=(const unsigned char*)(text!=NULL?text:"");memset(&b,0,sizeof(b));if(!b2026_pdf_text_append_char(&b,'/'))goto fail;while(*p){unsigned char c=*p++;if(c<=32U||c>=127U||strchr("#%()/<>[]{}",(int)c)!=NULL){char x[3]={'#',hex[c>>4],hex[c&15]};if(!b2026_pdf_text_append_bytes(&b,x,3U))goto fail;}else if(!b2026_pdf_text_append_char(&b,(char)c))goto fail;}return b2026_pdf_text_take(&b);fail:free(b.data);return NULL;}

static int b2026_pdf_attachment_rebuild_names(B2026PdfDocument *doc,B2026PdfAttachmentList *list,const unsigned char *keep){
    B2026PdfTextBuffer tree;const unsigned char *names=NULL;size_t names_n=0U;
    int names_ref=-1,names_obj,i,kept=0;char *names_copy=NULL,*tree_value=NULL,ref[64];
    memset(&tree,0,sizeof(tree));if(doc->root_object<0)return 0;
    if(b2026_pdf_attachment_resolve_key(doc,doc->root_object,"/Names",&names_ref,&names,&names_n)){
        names_copy=(char*)malloc(names_n+1U);if(names_copy==NULL)return 0;
        memcpy(names_copy,names,names_n);names_copy[names_n]='\0';
    }else names_copy=b2026_strdup_simple("<< >>");
    if(names_copy==NULL)return 0;names_obj=b2026_pdf_tf_add_body(doc,names_copy);free(names_copy);if(names_obj<0)return 0;
    if(!b2026_pdf_text_append_bytes(&tree,"<< /Names [",11U))goto fail;
    for(i=0;i<list->count;++i)if(!list->items[i].is_annotation&&(keep==NULL||keep[i])){
        char *key=b2026_pdf_attachment_pdf_string(list->items[i].name);char item_ref[64];
        if(key==NULL)goto fail;snprintf(item_ref,sizeof(item_ref)," %d 0 R",doc->objects[list->items[i].filespec_index].old_number);
        if(!b2026_pdf_text_append_char(&tree,' ')||!b2026_pdf_text_append_bytes(&tree,key,strlen(key))||!b2026_pdf_text_append_bytes(&tree,item_ref,strlen(item_ref))){free(key);goto fail;}
        free(key);++kept;
    }
    if(!b2026_pdf_text_append_bytes(&tree," ] >>",5U))goto fail;tree_value=b2026_pdf_text_take(&tree);
    if(kept>0){if(!b2026_pdf_tf_set_key(doc,names_obj,"/EmbeddedFiles",tree_value,0))goto fail;}
    else if(!b2026_pdf_tf_set_key(doc,names_obj,"/EmbeddedFiles",NULL,1))goto fail;
    snprintf(ref,sizeof(ref),"%d 0 R",doc->objects[names_obj].old_number);
    if(!b2026_pdf_tf_set_key(doc,doc->root_object,"/Names",ref,0))goto fail;
    free(tree_value);return 1;
fail:free(tree.data);free(tree_value);return 0;
}

static int b2026_pdf_attachment_remove_annots(B2026PdfDocument *doc,B2026PdfAttachmentList *list,const unsigned char *keep){
    int p,i;
    for(p=0;p<doc->page_count;++p){
        int page=doc->page_objects[p],ref,failed=0,changed=0;
        const unsigned char *v;size_t n,pos=0U;B2026PdfTextBuffer out;
        memset(&out,0,sizeof(out));
        if(!b2026_pdf_attachment_resolve_key(doc,page,"/Annots",&ref,&v,&n))continue;
        if(!b2026_pdf_text_append_char(&out,'['))failed=1;
        while(!failed&&pos<n){
            size_t ve;int ai,drop=0;pos=b2026_pdf_tf_space(v,n,pos);if(pos>=n)break;
            if(v[pos]=='['||v[pos]==']'){++pos;continue;}
            ve=b2026_pdf_tf_value_end(v,n,pos,0);if(ve<=pos||ve>n){failed=1;break;}
            ai=b2026_pdf_attachment_ref_value(doc,v+pos,ve-pos);
            for(i=0;i<list->count;++i)if(list->items[i].is_annotation&&list->items[i].annotation_index==ai&&!keep[i]){drop=1;changed=1;break;}
            if(!drop&&(!b2026_pdf_text_append_char(&out,' ')||!b2026_pdf_text_append_bytes(&out,(const char*)v+pos,ve-pos)))failed=1;
            pos=ve;
        }
        if(!failed&&!b2026_pdf_text_append_bytes(&out," ]",2U))failed=1;
        if(!failed&&changed&&!b2026_pdf_tf_set_key(doc,page,"/Annots",out.data,0))failed=1;
        free(out.data);if(failed)return 0;
    }
    return 1;
}

static int b2026_pdf_attachment_selector(B2026PdfAttachmentList *list,const B2026PdfArg *arg,unsigned char **remove_out){unsigned char *r=(unsigned char*)calloc((size_t)(list->count>0?list->count:1),1U);int i,index;if(r==NULL)return 0;if(b2026_pdf_arg_integer(arg,&index)){if(index<1||index>list->count){free(r);return 0;}r[index-1]=1U;}else if(arg->kind==1&&arg->text!=NULL){const char *s=arg->text;if(b2026_strcasecmp(s,"ALL")==0||strcmp(s,"すべて")==0||strcmp(s,"全て")==0){memset(r,1,(size_t)list->count);}else if(strspn(s,"0123456789,- ")==strlen(s)){B2026PdfArg a;unsigned char *picked=NULL;memset(&a,0,sizeof(a));a.kind=1;a.text=s;if(!b2026_pdf_parse_page_spec(&a,list->count,&picked)){free(r);return 0;}memcpy(r,picked,(size_t)list->count);free(picked);}else{for(i=0;i<list->count;++i)if(strcmp(list->items[i].name,s)==0)r[i]=1U;}}else{free(r);return 0;}for(i=0;i<list->count;++i)if(r[i]){*remove_out=r;return 1;}free(r);return 0;}

static int b2026_pdf_attachment_read_paths(const B2026PdfArg *arg,char ***paths,int *count){int i,n;char **out=NULL;*paths=NULL;*count=0;if(arg->kind==1&&arg->text!=NULL){out=(char**)calloc(1,sizeof(*out));if(out==NULL)return 0;out[0]=b2026_strdup_simple(arg->text);if(out[0]==NULL){free(out);return 0;}*paths=out;*count=1;return 1;}if(arg->kind!=2||arg->array_name==NULL)return 0;{ArrayVariable *a=require_array_variable(arg->array_name);n=array_logical_count(a);out=(char**)calloc((size_t)(n>0?n:1),sizeof(*out));if(out==NULL)return 0;for(i=0;i<n;++i){B2026_Var *v=&a->data[option_base+i];if(!b2026_var_is_string(v)){while(i>0)free(out[--i]);free(out);return 0;}out[i]=b2026_strdup_simple(b2026_var_as_text(v));if(out[i]==NULL){while(i>=0)free(out[i--]);free(out);return 0;}}*paths=out;*count=n;return 1;}}
static void b2026_pdf_attachment_paths_free(char **p,int n){int i;for(i=0;i<n;++i)free(p[i]);free(p);}

static int b2026_pdf_attachment_add_files(B2026PdfDocument *doc,B2026PdfAttachmentList *list,char **paths,int path_count,HashVariable *h,const char *source){const char *policy=h!=NULL?b2026_pdf_tf_text(h,"同名添付","existingAttachment"):NULL,*desc=h!=NULL?b2026_pdf_tf_text(h,"説明","description"):NULL,*mime=h!=NULL?b2026_pdf_tf_text(h,"MIMEタイプ","mimeType"):NULL,*rel=h!=NULL?b2026_pdf_tf_text(h,"関連種別","relationship"):NULL,*override_name=h!=NULL?b2026_pdf_tf_text(h,"ファイル名","filename"):NULL;int pi;if(policy==NULL)policy="REPLACE";if(rel==NULL)rel="UNSPECIFIED";if(!b2026_pdf_attachment_policy_ok(policy)){b2026_pdf_attachment_error(4278,source,"existing-attachment policy must be REPLACE, RENAME, or ERROR / 同名添付の設定はREPLACE・RENAME・ERRORのいずれかです");return 0;}if(!b2026_pdf_attachment_relationship_ok(rel)){b2026_pdf_attachment_error(4279,source,"invalid attachment relationship / 添付ファイルの関連種別が正しくありません");return 0;}for(pi=0;pi<path_count;++pi){unsigned char *bytes=NULL;size_t n=0U;const char *base,*q;char *safe=NULL,*final_name=NULL,*name_pdf=NULL,*desc_pdf=NULL,*mime_name=NULL;int i,stream,fs;unsigned char digest[16];B2026PdfMd5 md5;char hex[33],dict[1024];B2026PdfTextBuffer body;memset(&body,0,sizeof(body));if(!b2026_pdf_read_file(paths[pi],&bytes,&n)){b2026_pdf_attachment_error(4280,paths[pi],"attachment source could not be read or exceeds 2 GiB / 添付元ファイルを読み込めないか2GiB上限を超えています");return 0;}base=paths[pi];for(q=base;*q;++q)if(*q=='/'||*q=='\\')base=q+1;safe=b2026_pdf_attachment_safe_name(pi==0&&override_name!=NULL?override_name:base,pi+1);if(safe==NULL){free(bytes);return 0;}final_name=b2026_strdup_simple(safe);if(final_name==NULL){free(bytes);free(safe);return 0;}for(i=0;i<list->count;++i)if(!list->items[i].is_annotation&&strcmp(list->items[i].name,final_name)==0){if(b2026_strcasecmp(policy,"ERROR")==0){b2026_pdf_attachment_error(4281,source,"attachment with the same name already exists / 同名の添付ファイルがあります");free(bytes);free(safe);free(final_name);return 0;}if(b2026_strcasecmp(policy,"RENAME")==0){int serial=2;char candidate[320];do{snprintf(candidate,sizeof(candidate),"%s_%d",safe,serial++);for(i=0;i<list->count;++i)if(!list->items[i].is_annotation&&strcmp(list->items[i].name,candidate)==0)break;}while(i<list->count&&serial<1000000);free(final_name);final_name=b2026_strdup_simple(candidate);}else{free(list->items[i].name);list->items[i].name=b2026_strdup_simple("__B2026_REPLACED__");}break;}b2026_pdf_md5_init(&md5);b2026_pdf_md5_update(&md5,bytes,n);b2026_pdf_md5_final(&md5,digest);for(i=0;i<16;++i)snprintf(hex+i*2,3U,"%02x",digest[i]);mime_name=b2026_pdf_attachment_pdf_name(mime!=NULL?mime:"application/octet-stream");snprintf(dict,sizeof(dict),"/Type /EmbeddedFile /Subtype %s /Params << /Size %lu /CheckSum <%s> >>",mime_name!=NULL?mime_name:"/application#2Foctet-stream",(unsigned long)n,hex);stream=b2026_pdf_tf_add_stream_bytes(doc,dict,bytes,n);free(bytes);free(mime_name);if(stream<0){free(safe);free(final_name);return 0;}name_pdf=b2026_pdf_attachment_pdf_string(final_name);desc_pdf=desc!=NULL?b2026_pdf_attachment_pdf_string(desc):NULL;if(name_pdf==NULL||(desc!=NULL&&desc_pdf==NULL)){free(safe);free(final_name);free(name_pdf);free(desc_pdf);return 0;}if(!b2026_pdf_text_append_bytes(&body,"<< /Type /Filespec /F ",21U)||!b2026_pdf_text_append_bytes(&body,name_pdf,strlen(name_pdf))||!b2026_pdf_text_append_bytes(&body," /UF ",5U)||!b2026_pdf_text_append_bytes(&body,name_pdf,strlen(name_pdf)))goto addfail;if(desc_pdf!=NULL&&(!b2026_pdf_text_append_bytes(&body," /Desc ",7U)||!b2026_pdf_text_append_bytes(&body,desc_pdf,strlen(desc_pdf))))goto addfail;{char tail[192];snprintf(tail,sizeof(tail)," /AFRelationship /%s /EF << /F %d 0 R /UF %d 0 R >> >>",rel,doc->objects[stream].old_number,doc->objects[stream].old_number);if(!b2026_pdf_text_append_bytes(&body,tail,strlen(tail)))goto addfail;}fs=b2026_pdf_tf_add_body(doc,body.data);if(fs<0)goto addfail;if(!b2026_pdf_attachment_push(list,final_name,fs,stream,-1,0,0))goto addfail;free(body.data);free(name_pdf);free(desc_pdf);free(safe);free(final_name);continue;addfail:free(body.data);free(name_pdf);free(desc_pdf);free(safe);free(final_name);return 0;}return 1;}

static int b2026_pdf_attachment_verify(const char *path,int expected){B2026PdfDocument d;B2026PdfAttachmentList l;int rc,ok=0,collected=0;char detail[256];memset(&d,0,sizeof(d));memset(&l,0,sizeof(l));rc=b2026_pdf_load(&d,path,0);if(rc>0&&(collected=b2026_pdf_attachment_collect(&d,&l))&&l.count==expected)ok=1;if(!ok){if(rc<=0)snprintf(detail,sizeof(detail),"saved PDF could not be reopened: %s",b2026_pdf_load_error(rc));else if(!collected)snprintf(detail,sizeof(detail),"saved attachment tree is damaged / 保存後の添付ツリーが破損しています");else snprintf(detail,sizeof(detail),"saved attachment count mismatch: expected=%d actual=%d / 保存後の添付数が一致しません",expected,l.count);b2026_pdf_attachment_error(4287,path,detail);}b2026_pdf_attachment_list_free(&l);b2026_pdf_document_free(&d);return ok;}
static const char *b2026_pdf_attachment_save(B2026PdfDocument *doc,const char *output,int expected){
    B2026PdfPageList pages;char *stage=NULL;int i;const char *r;memset(&pages,0,sizeof(pages));
    /* The writer emits fresh /Type and /Pages entries for the output catalog.
     * Remove their source copies before preserving the other catalog keys so
     * strict readers never see duplicate dictionary keys. */
    if(doc->root_object<0||!b2026_pdf_tf_set_key(doc,doc->root_object,"/Type",NULL,1)||!b2026_pdf_tf_set_key(doc,doc->root_object,"/Pages",NULL,1))goto fail;
    for(i=0;i<doc->page_count;++i)if(!b2026_pdf_pages_push(&pages,doc,doc->page_objects[i],-1))goto fail;
    stage=b2026_atomic_temp_path(output);
    if(stage==NULL||!b2026_pdf_write_output(stage,doc,1,&pages,0,1,1)||!b2026_pdf_attachment_verify(stage,expected)||!b2026_atomic_commit_file(stage,output))goto fail;
    r=runtime_temp_string_copy(output);free(stage);free(pages.items);return r;
fail:if(stage!=NULL){b2026_atomic_discard_file(stage);free(stage);}free(pages.items);b2026_pdf_attachment_error(4288,output,"attachment PDF could not be saved or failed verification / 添付処理PDFを保存できないか保存後検証に失敗しました");return runtime_temp_string_copy("");
}

static const char *b2026_pdf_attachment_edit(const char *operation,int argc,const B2026PdfArg *args){if(strcmp(operation,"ATTACHMENTEXTRACT")==0){B2026PdfDocument doc;B2026PdfAttachmentList list;HashVariable *h=NULL;const char *policy="RENAME",*dest;unsigned char *data=NULL;size_t n=0U;char *actual=NULL;int rc,index;memset(&doc,0,sizeof(doc));memset(&list,0,sizeof(list));if((argc!=3&&argc!=4)||args[0].kind!=1||args[0].text==NULL||args[2].kind!=1||args[2].text==NULL||(argc==4&&!b2026_pdf_tf_hash(&args[3],&h)))return b2026_pdf_attachment_error_string(4290,"","invalid EXTRACTATTACHMENT arguments / 添付抽出の引数が正しくありません");if(h!=NULL){const char*p=b2026_pdf_tf_text(h,"既存ファイル","existingFile");if(p!=NULL)policy=p;}rc=b2026_pdf_load(&doc,args[0].text,0);if(rc<=0){b2026_pdf_attachment_error(4291,args[0].text,b2026_pdf_load_error(rc));goto extract_fail;}if(!b2026_pdf_attachment_collect(&doc,&list)||(index=b2026_pdf_attachment_select(&list,&args[1]))<0){b2026_pdf_attachment_error(4292,args[0].text,"attachment was not found or attachment tree is damaged / 添付がないか添付ツリーが破損しています");goto extract_fail;}if(!b2026_pdf_attachment_stream_decode(&doc,&list.items[index],&data,&n)){b2026_pdf_attachment_error(4293,args[0].text,list.items[index].error_message);goto extract_fail;}dest=args[2].text;actual=b2026_pdf_attachment_choose_path(dest,policy);if(actual==NULL||!b2026_pdf_attachment_write(actual,data,n)){b2026_pdf_attachment_error(4294,dest,"attachment could not be written safely / 添付ファイルを安全に保存できません");goto extract_fail;}{const char*r=runtime_temp_string_copy(actual);free(actual);free(data);b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return r;}extract_fail:free(actual);free(data);b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return runtime_temp_string_copy("");}
    else{int add=strcmp(operation,"ATTACHMENTADD")==0,separate,source_index,subject_index,settings_index=-1,rc,i;const char *source;char *output=NULL,*stage_result=NULL;HashVariable *h=NULL;B2026PdfDocument doc;B2026PdfAttachmentList list;unsigned char *remove=NULL,*keep=NULL;char **paths=NULL;int path_count=0,expected=0;memset(&doc,0,sizeof(doc));memset(&list,0,sizeof(list));if(add){if(argc<2||argc>4)return b2026_pdf_attachment_error_string(4295,"","invalid ATTACHMENTADD arguments / 添付追加の引数が正しくありません");separate=(argc>=3&&args[argc-1].kind!=3);if(argc==4)separate=1;source_index=separate?1:0;subject_index=separate?2:1;if(args[argc-1].kind==3)settings_index=argc-1;}else{if(argc!=2&&argc!=3)return b2026_pdf_attachment_error_string(4295,"","invalid ATTACHMENTDELETE arguments / 添付削除の引数が正しくありません");separate=argc==3;source_index=separate?1:0;subject_index=separate?2:1;}if(args[0].kind!=1||args[0].text==NULL||args[source_index].kind!=1||args[source_index].text==NULL)return b2026_pdf_attachment_error_string(4295,"","PDF path is required / PDFパスが必要です");if(settings_index>=0&&!b2026_pdf_tf_hash(&args[settings_index],&h))return b2026_pdf_attachment_error_string(4295,args[0].text,"settings hash was not found / 設定連想配列がありません");output=b2026_pdf_output_path(args[0].text);source=args[source_index].text;if(output==NULL)goto edit_memory;rc=b2026_pdf_load(&doc,source,4);if(rc<=0){b2026_pdf_attachment_error(4296,source,b2026_pdf_load_error(rc));goto edit_done;}if(!b2026_pdf_attachment_collect(&doc,&list)){b2026_pdf_attachment_error(4297,source,"attachment tree is damaged / 添付ツリーが破損しています");goto edit_done;}if(add){if(!b2026_pdf_attachment_read_paths(&args[subject_index],&paths,&path_count)||path_count<1||!b2026_pdf_attachment_add_files(&doc,&list,paths,path_count,h,source))goto edit_done;keep=(unsigned char*)calloc((size_t)list.count,1U);if(keep==NULL)goto edit_memory;for(i=0;i<list.count;++i)keep[i]=strcmp(list.items[i].name,"__B2026_REPLACED__")!=0;if(!b2026_pdf_attachment_rebuild_names(&doc,&list,keep))goto edit_memory;expected=0;for(i=0;i<list.count;++i)if(keep[i])++expected;}else{if(!b2026_pdf_attachment_selector(&list,&args[subject_index],&remove)){b2026_pdf_attachment_error(4298,source,"attachment selector did not match / 添付指定に一致するファイルがありません");goto edit_done;}keep=(unsigned char*)malloc((size_t)list.count);if(keep==NULL)goto edit_memory;for(i=0;i<list.count;++i)keep[i]=remove[i]?0U:1U;if(!b2026_pdf_attachment_rebuild_names(&doc,&list,keep)||!b2026_pdf_attachment_remove_annots(&doc,&list,keep))goto edit_memory;for(i=0;i<list.count;++i)if(keep[i])++expected;}stage_result=(char*)b2026_pdf_attachment_save(&doc,output,expected);{const char*r=runtime_temp_string_copy(stage_result!=NULL?stage_result:"");b2026_pdf_attachment_paths_free(paths,path_count);free(remove);free(keep);free(output);b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return r;}edit_memory:b2026_pdf_attachment_error(4299,source,"out of memory / メモリ不足です");edit_done:b2026_pdf_attachment_paths_free(paths,path_count);free(remove);free(keep);free(output);b2026_pdf_attachment_list_free(&list);b2026_pdf_document_free(&doc);return runtime_temp_string_copy("");}}
