aboutsummaryrefslogblamecommitdiffstats
path: root/file.c
blob: d4c47fd7a4b1daafcb0d910ae57cd7e3478e935f (plain) (tree)
1
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
5572
5573
5574
5575
5576
5577
5578
5579
5580
5581
5582
5583
5584
5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
5633
5634
5635
5636
5637
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678
5679
5680
5681
5682
5683
5684
5685
5686
5687
5688
5689
5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
5728
5729
5730
5731
5732
5733
5734
5735
5736
5737
5738
5739
5740
5741
5742
5743
5744
5745
5746
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
5811
5812
5813
5814
5815
5816
5817
5818
5819
5820
5821
5822
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851
5852
5853
5854
5855
5856
5857
5858
5859
5860
5861
5862
5863
5864
5865
5866
5867
5868
5869
5870
5871
5872
5873
5874
5875
5876
5877
5878
5879
5880
5881
5882
5883
5884
5885
5886
5887
5888
5889
5890
5891
5892
5893
5894
5895
5896
5897
5898
5899
5900
5901
5902
5903
5904
5905
5906
5907
5908
5909
5910
5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
5926
5927
5928
5929
5930
5931
5932
5933
5934
5935
5936
5937
5938
5939
5940
5941
5942
5943
5944
5945
5946
5947
5948
5949
5950
5951
5952
5953
5954
5955
5956
5957
5958
5959
5960
5961
5962
5963
5964
5965
5966
5967
5968
5969
5970
5971
5972
5973
5974
5975
5976
5977
5978
5979
5980
5981
5982
5983
5984
5985
5986
5987
5988
5989
5990
5991
5992
5993
5994
5995
5996
5997
5998
5999
6000
6001
6002
6003
6004
6005
6006
6007
6008
6009
6010
6011
6012
6013
6014
6015
6016
6017
6018
6019
6020
6021
6022
6023
6024
6025
6026
6027
6028
6029
6030
6031
6032
6033
6034
6035
6036
6037
6038
6039
6040
6041
6042
6043
6044
6045
6046
6047
6048
6049
6050
6051
6052
6053
6054
6055
6056
6057
6058
6059
6060
6061
6062
6063
6064
6065
6066
6067
6068
6069
6070
6071
6072
6073
6074
6075
6076
6077
6078
6079
6080
6081
6082
6083
6084
6085
6086
6087
6088
6089
6090
6091
6092
6093
6094
6095
6096
6097
6098
6099
6100
6101
6102
6103
6104
6105
6106
6107
6108
6109
6110
6111
6112
6113
6114
6115
6116
6117
6118
6119
6120
6121
6122
6123
6124
6125
6126
6127
6128
6129
6130
6131
6132
6133
6134
6135
6136
6137
6138
6139
6140
6141
6142
6143
6144
6145
6146
6147
6148
6149
6150
6151
6152
6153
6154
6155
6156
6157
6158
6159
6160
6161
6162
6163
6164
6165
6166
6167
6168
6169
6170
6171
6172
6173
6174
6175
6176
6177
6178
6179
6180
6181
6182
6183
6184
6185
6186
6187
6188
6189
6190
6191
6192
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203
6204
6205
6206
6207
6208
6209
6210
6211
6212
6213
6214
6215
6216
6217
6218
6219
6220
6221
6222
6223
6224
6225
6226
6227
6228
6229
6230
6231
6232
6233
6234
6235
6236
6237
6238
6239
6240
6241
6242
6243
6244
6245
6246
6247
6248
6249
6250
6251
6252
6253
6254
6255
6256
6257
6258
6259
6260
6261
6262
6263
6264
6265
6266
6267
6268
6269
6270
6271
6272
6273
6274
6275
6276
6277
6278
6279
6280
6281
6282
6283
6284
6285
6286
6287
6288
6289
6290
6291
6292
6293
6294
6295
6296
6297
6298
6299
6300
6301
6302
6303
6304
6305
6306
6307
6308
6309
6310
6311
6312
6313
6314
6315
6316
6317
6318
6319
6320
6321
6322
6323
6324
6325
6326
6327
6328
6329
6330
6331
6332
6333
6334
6335
6336
6337
6338
6339
6340
6341
6342
6343
6344
6345
6346
6347
6348
6349
6350
6351
6352
6353
6354
6355
6356
6357
6358
6359
6360
6361
6362
6363
6364
6365
6366
6367
6368
6369
6370
6371
6372
6373
6374
6375
6376
6377
6378
6379
6380
6381
6382
6383
6384
6385
6387
6388
6389
6390
6391
6392
6393
6394
6395
6396
6397
6398
6399
6400
6401
6402
6403
6404
6405
6406
6407
6408
6409
6410
6411
6412
6413
6414
6415
6416
6417
6418
6419
6420
6421
6422
6423
6424
6425
6426
6427
6428
6429
6430
6431
6432
6433
6434
6435
6436
6437
6438
6439
6440
6441
6442
6443
6444
6445
6446
6447
6448
6449
6450
6451
6452
6453
6454
6455
6456
6457
6458
6459
6460
6461
6462
6463
6464
6465
6466
6467
6468
6469
6470
6471
6472
6473
6474
6475
6476
6477
6478
6479
6480
6481
6482
6483
6484
6485
6486
6487
6488
6489
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
6504
6505
6506
6507
6508
6509
6510
6511
6512
6513
6514
6515
6516
6517
6518
6519
6520
6521
6522
6523
6524
6525
6526
6527
6528
6529
6530
6531
6532
6533
6534
6535
6536
6537
6538
6539
6540
6541
6542
6543
6544
6545
6546
6547
6548
6549
6550
6551
6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562
6563
6564
6565
6566
6567
6568
6569
6570
6571
6572
6573
6574
6575
6576
6577
6578
6579
6580
6581
6582
6583
6584
6585
6586
6587
6588
6589
6590
6591
6592
6593
6594
6595
6596
6597
6598
6599
6600
6601
6602
6603
6604
6605
6606
6607
6608
6609
6610
6611
6612
6613
6614
6615
6616
6617
6618
6619
6620
6621
6622
6623
6624
6625
6626
6627
6628
6629
6630
6631
6632
6633
6634
6635
6636
6637
6638
6639
6640
6641
6642
6643
6644
6645
6646
6647
6648
6649
6650
6651
6652
6653
6654
6655
6656
6657
6658
6659
6660
6661
6662
6663
6664
6665
6666
6667
6668
6669
6670
6671
6672
6673
6674
6675
6676
6677
6678
6679
6680
6681
6682
6683
6684
6685
6686
6687
6688
6689
6690
6691
6692
6693
6694
6695
6696
6697
6698
6699
6700
6701
6702
6703
6704
6705
6706
6707
6708
6709
6710
6711
6712
6713
6714
6715
6716
6717
6718
6719
6720
6721
6722
6723
6724
6725
6726
6727
6728
6729
6730
6731
6732
6733
6734
6735
6736
6737
6738
6739
6740
6741
6742
6743
6744
6745
6746
6747
6748
6749
6750
6751
6752
                                                        






































































































































































































































































                                                                                                         

                                                          









                                                                        






                                                                      




































































































                                                                       
                                             















































































































































































































































                                                                             
                       
                     
                                        


                              





























































































































                                                                                
                                                   























































































                                                                                             


                                                                                
                                                   

                                                                           

                                                                          
                 






























                                                                                          

                                                                           

                        

                                                     





























































































                                                                              




















































                                          



















                                             

                     




                     









                      
                     












                                                






























                                                    
            







                                                 







                                                      
                             
                          





                                               













                                                                      
                           



                          






















































                                                                     
                            










                                                     





                 




                                                                      
                                             




                                                                         

                                                                         

                                                             

                  
                                         




































                                                                                             






























                                                                               
                                
                                                                  
                                       












                                                                                                    

             




                                     



                                         
                            











                                                                       
                                                                          







































                                                                               
                            
                                     

                                                               
                               

                                                                                                           




























































                                                                          
                                       

                    







                                                                   



                                                                

                                                   




                                                                    





                                                                       






                                                                                              








































































































                                                                               
                         


                                                                    






                                                                            

































































                                                                               





                                                  







































































































































































































































                                                                               
                                                                                       









































































































                                                                        




















                                                                              
















                                      



                                                                    












































































                                                                         

                                                                


                                               




























                                                              
                        






























































                                                                              
                            


























                                                                               
                                   






























































































































































































                                                                       
                                         
                                       

                                         












                                                          
                                         
                                       

                                         




















                                                 

                    





















































































































































































































                                                                               

                                                                           




































                                                                               





                                                                      





                                                                     











                                                                      






















                                                                       

                                          

                                        



                                          









































































































































































































                                                                            

                                          

                                        



                                          













































































































                                                                               

                                         

                                         



                                             












                                                                            

                                      

                                      



                                          



















































                                                                  
                                             






                                                

                                                                             















































                                                                   

                                            



































































































































































































































                                                                               
                                





































































































                                                                            

                                             


                              

                                             
































                                                                              
                                               





                                                            

                                                  





















































































                                                              



                                                             
                                                                                  
                                                                                




























































































































































                                                                               

                                   













                                                                    
                                     
                                   


                                                               



                                                  
                                     
                                  


                                                                







































































                                                                       
                               


                              



                                                   
                                 
            
                  
     



































































































































































































































































































































                                                                                













                                                                














                                                               
                                    

                   





                                     

























































                                                                              
                                                               

























































                                                                              

                                                
































                                                                              
                                                                                     






















































































































































                                                                              
                                 

























































































































                                                                              

                                                                































































































































































































































































                                                                               

                                    
                                 










                                                 

                    

                                    
                                  















                                                                    

                 

                                    
                               










                                               

                  

                                    
                                















                                                                    

                  



                                    
                                                 







                                                  

                    



                                    
                                                 













                                                                   





























































































                                                                               
          


















                                 







                                                          
                                     













































                                                            
                  























                                                                      
                                                                           






                                                       
                                                                  



                                                           
                                                                          


                     
                                                                                    







                                                            
                                                                   



                                         
                                                                   






                                                  
                                                                    




                                                           
                                                                            
















                                                               
                                                                           



                                                 
                                                                           






                                               
                                                                          




                                                                   
                                                                                  

















                                                   











                                               





                                        





                                               
































                                                                         










                                                                           


















                                                                        



                                                                                
                                              
                         













































































































































































































































































































































































































































































































































































































































































































































































































































                                                                               
                                                                    













































































































































































































































































































































































                                                                                 
                        
                       

                        

































                                                                            



                                         



                                         







                                             































































































































































































                                                                              

                                                                                  























                                                                       

                                                                                                  



























































































































































































































































                                                                               

                                                                                                  

























































































































































                                                                               
                                      



























































































































































                                                                            
                          














































































































































































































                                                                            






                                                              






























































































































































                                                                               
                         









































































































                                                                       
                              













                                       
                         




                
                       







































                                                                        
                    
                                                                            



                                              
                                                 

                                                                  


                                          

                           


























                                                                      
                                                                      



                                          







                                                  
                              



























































                                                             
                             








                                          
                         

















































































                                                                    
                                                       
                              


































































                                                                            
                                





















































                                                                     
/* $Id: file.c,v 1.265 2010/12/15 10:50:24 htrb Exp $ */
#include "fm.h"
#include <sys/types.h>
#include "myctype.h"
#include <signal.h>
#include <setjmp.h>
#if defined(HAVE_WAITPID) || defined(HAVE_WAIT3)
#include <sys/wait.h>
#endif
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
/* foo */

#include "html.h"
#include "parsetagx.h"
#include "local.h"
#include "regex.h"

#ifndef max
#define max(a,b)        ((a) > (b) ? (a) : (b))
#endif				/* not max */
#ifndef min
#define min(a,b)        ((a) > (b) ? (b) : (a))
#endif				/* not min */

static int frame_source = 0;

static char *guess_filename(char *file);
static int _MoveFile(char *path1, char *path2);
static void uncompress_stream(URLFile *uf, char **src);
static FILE *lessopen_stream(char *path);
static Buffer *loadcmdout(char *cmd,
			  Buffer *(*loadproc) (URLFile *, Buffer *),
			  Buffer *defaultbuf);
#ifndef USE_ANSI_COLOR
#define addnewline(a,b,c,d,e,f,g) _addnewline(a,b,c,e,f,g)
#endif
static void addnewline(Buffer *buf, char *line, Lineprop *prop,
		       Linecolor *color, int pos, int width, int nlines);
static void addLink(Buffer *buf, struct parsed_tag *tag);

static JMP_BUF AbortLoading;

static struct table *tables[MAX_TABLE];
static struct table_mode table_mode[MAX_TABLE];

#ifdef USE_IMAGE
static ParsedURL *cur_baseURL = NULL;
#ifdef USE_M17N
static char cur_document_charset;
#endif
#endif

static Str cur_title;
static Str cur_select;
static Str select_str;
static int select_is_multiple;
static int n_selectitem;
static Str cur_option;
static Str cur_option_value;
static Str cur_option_label;
static int cur_option_selected;
static int cur_status;
#ifdef MENU_SELECT
/* menu based <select>  */
FormSelectOption *select_option;
static int max_select = MAX_SELECT;
static int n_select;
static int cur_option_maxwidth;
#endif				/* MENU_SELECT */

static Str cur_textarea;
Str *textarea_str;
static int cur_textarea_size;
static int cur_textarea_rows;
static int cur_textarea_readonly;
static int n_textarea;
static int ignore_nl_textarea;
static int max_textarea = MAX_TEXTAREA;

static int http_response_code;

#ifdef USE_M17N
static wc_ces content_charset = 0;
static wc_ces meta_charset = 0;
static char *check_charset(char *p);
static char *check_accept_charset(char *p);
#endif

#define set_prevchar(x,y,n) Strcopy_charp_n((x),(y),(n))
#define set_space_to_prevchar(x) Strcopy_charp_n((x)," ",1)

struct link_stack {
    int cmd;
    short offset;
    short pos;
    struct link_stack *next;
};

static struct link_stack *link_stack = NULL;

#define FORMSTACK_SIZE 10
#define FRAMESTACK_SIZE 10

#ifdef USE_NNTP
#define Str_news_endline(s) ((s)->ptr[0]=='.'&&((s)->ptr[1]=='\n'||(s)->ptr[1]=='\r'||(s)->ptr[1]=='\0'))
#endif				/* USE_NNTP */

#define INITIAL_FORM_SIZE 10
static FormList **forms;
static int *form_stack;
static int form_max = -1;
static int forms_size = 0;
#define cur_form_id ((form_sp >= 0)? form_stack[form_sp] : -1)
static int form_sp = 0;

static clen_t current_content_length;

static int cur_hseq;
#ifdef USE_IMAGE
static int cur_iseq;
#endif

#define MAX_UL_LEVEL	9
#define UL_SYMBOL(x)	(N_GRAPH_SYMBOL + (x))
#define UL_SYMBOL_DISC		UL_SYMBOL(9)
#define UL_SYMBOL_CIRCLE	UL_SYMBOL(10)
#define UL_SYMBOL_SQUARE	UL_SYMBOL(11)
#define IMG_SYMBOL		UL_SYMBOL(12)
#define HR_SYMBOL	26

#ifdef USE_COOKIE
/* This array should be somewhere else */
/* FIXME: gettextize? */
char *violations[COO_EMAX] = {
    "internal error",
    "tail match failed",
    "wrong number of dots",
    "RFC 2109 4.3.2 rule 1",
    "RFC 2109 4.3.2 rule 2.1",
    "RFC 2109 4.3.2 rule 2.2",
    "RFC 2109 4.3.2 rule 3",
    "RFC 2109 4.3.2 rule 4",
    "RFC XXXX 4.3.2 rule 5"
};
#endif

/* *INDENT-OFF* */
static struct compression_decoder {
    int type;
    char *ext;
    char *mime_type;
    int auxbin_p;
    char *cmd;
    char *name;
    char *encoding;
    char *encodings[4];
} compression_decoders[] = {
    { CMP_COMPRESS, ".gz", "application/x-gzip",
      0, GUNZIP_CMDNAME, GUNZIP_NAME, "gzip", 
      {"gzip", "x-gzip", NULL} }, 
    { CMP_COMPRESS, ".Z", "application/x-compress",
      0, GUNZIP_CMDNAME, GUNZIP_NAME, "compress",
      {"compress", "x-compress", NULL} }, 
    { CMP_BZIP2, ".bz2", "application/x-bzip",
      0, BUNZIP2_CMDNAME, BUNZIP2_NAME, "bzip, bzip2",
      {"x-bzip", "bzip", "bzip2", NULL} }, 
    { CMP_DEFLATE, ".deflate", "application/x-deflate",
      1, INFLATE_CMDNAME, INFLATE_NAME, "deflate",
      {"deflate", "x-deflate", NULL} }, 
    { CMP_NOCOMPRESS, NULL, NULL, 0, NULL, NULL, NULL, {NULL}},
};
/* *INDENT-ON* */

#define SAVE_BUF_SIZE 1536

static MySignalHandler
KeyAbort(SIGNAL_ARG)
{
    LONGJMP(AbortLoading, 1);
    SIGNAL_RETURN;
}

static void
UFhalfclose(URLFile *f)
{
    switch (f->scheme) {
    case SCM_FTP:
	closeFTP();
	break;
#ifdef USE_NNTP
    case SCM_NEWS:
    case SCM_NNTP:
	closeNews();
	break;
#endif
    default:
	UFclose(f);
	break;
    }
}

int
currentLn(Buffer *buf)
{
    if (buf->currentLine)
	/*     return buf->currentLine->real_linenumber + 1;      */
	return buf->currentLine->linenumber + 1;
    else
	return 1;
}

static Buffer *
loadSomething(URLFile *f,
	      char *path,
	      Buffer *(*loadproc) (URLFile *, Buffer *), Buffer *defaultbuf)
{
    Buffer *buf;

    if ((buf = loadproc(f, defaultbuf)) == NULL)
	return NULL;

    buf->filename = path;
    if (buf->buffername == NULL || buf->buffername[0] == '\0') {
	buf->buffername = checkHeader(buf, "Subject:");
	if (buf->buffername == NULL)
	    buf->buffername = conv_from_system(lastFileName(path));
    }
    if (buf->currentURL.scheme == SCM_UNKNOWN)
	buf->currentURL.scheme = f->scheme;
    buf->real_scheme = f->scheme;
    if (f->scheme == SCM_LOCAL && buf->sourcefile == NULL)
	buf->sourcefile = path;
    return buf;
}

int
dir_exist(char *path)
{
    struct stat stbuf;

    if (path == NULL || *path == '\0')
	return 0;
    if (stat(path, &stbuf) == -1)
	return 0;
    return IS_DIRECTORY(stbuf.st_mode);
}

static int
is_dump_text_type(char *type)
{
    struct mailcap *mcap;
    return (type && (mcap = searchExtViewer(type)) &&
	    (mcap->flags & (MAILCAP_HTMLOUTPUT | MAILCAP_COPIOUSOUTPUT)));
}

static int
is_text_type(char *type)
{
    return (type == NULL || type[0] == '\0' ||
	    strncasecmp(type, "text/", 5) == 0 ||
	    (strncasecmp(type, "application/", 12) == 0 &&
		strstr(type, "xhtml") != NULL) ||
	    strncasecmp(type, "message/", sizeof("message/") - 1) == 0);
}

static int
is_plain_text_type(char *type)
{
    return ((type && strcasecmp(type, "text/plain") == 0) ||
	    (is_text_type(type) && !is_dump_text_type(type)));
}

int
is_html_type(char *type)
{
    return (type && (strcasecmp(type, "text/html") == 0 ||
		     strcasecmp(type, "application/xhtml+xml") == 0));
}

static void
check_compression(char *path, URLFile *uf)
{
    int len;
    struct compression_decoder *d;

    if (path == NULL)
	return;

    len = strlen(path);
    uf->compression = CMP_NOCOMPRESS;
    for (d = compression_decoders; d->type != CMP_NOCOMPRESS; d++) {
	int elen;
	if (d->ext == NULL)
	    continue;
	elen = strlen(d->ext);
	if (len > elen && strcasecmp(&path[len - elen], d->ext) == 0) {
	    uf->compression = d->type;
	    uf->guess_type = d->mime_type;
	    break;
	}
    }
}

static char *
compress_application_type(int compression)
{
    struct compression_decoder *d;

    for (d = compression_decoders; d->type != CMP_NOCOMPRESS; d++) {
	if (d->type == compression)
	    return d->mime_type;
    }
    return NULL;
}

static char *
uncompressed_file_type(char *path, char **ext)
{
    int len, slen;
    Str fn;
    char *t0;
    struct compression_decoder *d;

    if (path == NULL)
	return NULL;

    slen = 0;
    len = strlen(path);
    for (d = compression_decoders; d->type != CMP_NOCOMPRESS; d++) {
	if (d->ext == NULL)
	    continue;
	slen = strlen(d->ext);
	if (len > slen && strcasecmp(&path[len - slen], d->ext) == 0)
	    break;
    }
    if (d->type == CMP_NOCOMPRESS)
	return NULL;

    fn = Strnew_charp(path);
    Strshrink(fn, slen);
    if (ext)
	*ext = filename_extension(fn->ptr, 0);
    t0 = guessContentType(fn->ptr);
    if (t0 == NULL)
	t0 = "text/plain";
    return t0;
}

static int
setModtime(char *path, time_t modtime)
{
    struct utimbuf t;
    struct stat st;

    if (stat(path, &st) == 0)
	t.actime = st.st_atime;
    else
	t.actime = time(NULL);
    t.modtime = modtime;
    return utime(path, &t);
}

void
examineFile(char *path, URLFile *uf)
{
    struct stat stbuf;

    uf->guess_type = NULL;
    if (path == NULL || *path == '\0' ||
	stat(path, &stbuf) == -1 || NOT_REGULAR(stbuf.st_mode)) {
	uf->stream = NULL;
	return;
    }
    uf->stream = openIS(path);
    if (!do_download) {
	if (use_lessopen && getenv("LESSOPEN") != NULL) {
	    FILE *fp;
	    uf->guess_type = guessContentType(path);
	    if (uf->guess_type == NULL)
		uf->guess_type = "text/plain";
	    if (is_html_type(uf->guess_type))
		return;
	    if ((fp = lessopen_stream(path))) {
		UFclose(uf);
		uf->stream = newFileStream(fp, (void (*)())pclose);
		uf->guess_type = "text/plain";
		return;
	    }
	}
	check_compression(path, uf);
	if (uf->compression != CMP_NOCOMPRESS) {
	    char *ext = uf->ext;
	    char *t0 = uncompressed_file_type(path, &ext);
	    uf->guess_type = t0;
	    uf->ext = ext;
	    uncompress_stream(uf, NULL);
	    return;
	}
    }
}

#define S_IXANY	(S_IXUSR|S_IXGRP|S_IXOTH)

int
check_command(char *cmd, int auxbin_p)
{
    static char *path = NULL;
    Str dirs;
    char *p, *np;
    Str pathname;
    struct stat st;

    if (path == NULL)
	path = getenv("PATH");
    if (auxbin_p)
	dirs = Strnew_charp(w3m_auxbin_dir());
    else
	dirs = Strnew_charp(path);
    for (p = dirs->ptr; p != NULL; p = np) {
	np = strchr(p, PATH_SEPARATOR);
	if (np)
	    *np++ = '\0';
	pathname = Strnew();
	Strcat_charp(pathname, p);
	Strcat_char(pathname, '/');
	Strcat_charp(pathname, cmd);
	if (stat(pathname->ptr, &st) == 0 && S_ISREG(st.st_mode)
	    && (st.st_mode & S_IXANY) != 0)
	    return 1;
    }
    return 0;
}

char *
acceptableEncoding()
{
    static Str encodings = NULL;
    struct compression_decoder *d;
    TextList *l;
    char *p;

    if (encodings != NULL)
	return encodings->ptr;
    l = newTextList();
    for (d = compression_decoders; d->type != CMP_NOCOMPRESS; d++) {
	if (check_command(d->cmd, d->auxbin_p)) {
	    pushText(l, d->encoding);
	}
    }
    encodings = Strnew();
    while ((p = popText(l)) != NULL) {
	if (encodings->length)
	    Strcat_charp(encodings, ", ");
	Strcat_charp(encodings, p);
    }
    return encodings->ptr;
}

/* 
 * convert line
 */
#ifdef USE_M17N
Str
convertLine(URLFile *uf, Str line, int mode, wc_ces * charset,
	    wc_ces doc_charset)
#else
Str
convertLine0(URLFile *uf, Str line, int mode)
#endif
{
#ifdef USE_M17N
    line = wc_Str_conv_with_detect(line, charset, doc_charset, InnerCharset);
#endif
    if (mode != RAW_MODE)
	cleanup_line(line, mode);
#ifdef USE_NNTP
    if (uf && uf->scheme == SCM_NEWS)
	Strchop(line);
#endif				/* USE_NNTP */
    return line;
}

/* 
 * loadFile: load file to buffer
 */
Buffer *
loadFile(char *path)
{
    Buffer *buf;
    URLFile uf;
    init_stream(&uf, SCM_LOCAL, NULL);
    examineFile(path, &uf);
    if (uf.stream == NULL)
	return NULL;
    buf = newBuffer(INIT_BUFFER_WIDTH);
    current_content_length = 0;
#ifdef USE_M17N
    content_charset = 0;
#endif
    buf = loadSomething(&uf, path, loadBuffer, buf);
    UFclose(&uf);
    return buf;
}

int
matchattr(char *p, char *attr, int len, Str *value)
{
    int quoted;
    char *q = NULL;

    if (strncasecmp(p, attr, len) == 0) {
	p += len;
	SKIP_BLANKS(p);
	if (value) {
	    *value = Strnew();
	    if (*p == '=') {
		p++;
		SKIP_BLANKS(p);
		quoted = 0;
		while (!IS_ENDL(*p) && (quoted || *p != ';')) {
		    if (!IS_SPACE(*p))
			q = p;
		    if (*p == '"')
			quoted = (quoted) ? 0 : 1;
		    else
			Strcat_char(*value, *p);
		    p++;
		}
		if (q)
		    Strshrink(*value, p - q - 1);
	    }
	    return 1;
	}
	else {
	    if (IS_ENDT(*p)) {
		return 1;
	    }
	}
    }
    return 0;
}

#ifdef USE_IMAGE
#ifdef USE_XFACE
static char *
xface2xpm(char *xface)
{
    Image image;
    ImageCache *cache;
    FILE *f;
    struct stat st;

    SKIP_BLANKS(xface);
    image.url = xface;
    image.ext = ".xpm";
    image.width = 48;
    image.height = 48;
    image.cache = NULL;
    cache = getImage(&image, NULL, IMG_FLAG_AUTO);
    if (cache->loaded & IMG_FLAG_LOADED && !stat(cache->file, &st))
	return cache->file;
    cache->loaded = IMG_FLAG_ERROR;

    f = popen(Sprintf("%s > %s", shell_quote(auxbinFile(XFACE2XPM)),
		      shell_quote(cache->file))->ptr, "w");
    if (!f)
	return NULL;
    fputs(xface, f);
    pclose(f);
    if (stat(cache->file, &st) || !st.st_size)
	return NULL;
    cache->loaded = IMG_FLAG_LOADED | IMG_FLAG_DONT_REMOVE;
    cache->index = 0;
    return cache->file;
}
#endif
#endif

void
readHeader(URLFile *uf, Buffer *newBuf, int thru, ParsedURL *pu)
{
    char *p, *q;
#ifdef USE_COOKIE
    char *emsg;
#endif
    char c;
    Str lineBuf2 = NULL;
    Str tmp;
    TextList *headerlist;
#ifdef USE_M17N
    wc_ces charset = WC_CES_US_ASCII, mime_charset;
#endif
    char *tmpf;
    FILE *src = NULL;
    Lineprop *propBuffer;

    headerlist = newBuf->document_header = newTextList();
    if (uf->scheme == SCM_HTTP
#ifdef USE_SSL
	|| uf->scheme == SCM_HTTPS
#endif				/* USE_SSL */
	)
	http_response_code = -1;
    else
	http_response_code = 0;

    if (thru && !newBuf->header_source
#ifdef USE_IMAGE
	&& !image_source
#endif
	) {
	tmpf = tmpfname(TMPF_DFL, NULL)->ptr;
	src = fopen(tmpf, "w");
	if (src)
	    newBuf->header_source = tmpf;
    }
    while ((tmp = StrmyUFgets(uf))->length) {
#ifdef USE_NNTP
	if (uf->scheme == SCM_NEWS && tmp->ptr[0] == '.')
	    Strshrinkfirst(tmp, 1);
#endif
	if(w3m_reqlog){
	    FILE *ff;
	    ff = fopen(w3m_reqlog, "a");
	    Strfputs(tmp, ff);
	    fclose(ff);
	}
	if (src)
	    Strfputs(tmp, src);
	cleanup_line(tmp, HEADER_MODE);
	if (tmp->ptr[0] == '\n' || tmp->ptr[0] == '\r' || tmp->ptr[0] == '\0') {
	    if (!lineBuf2)
		/* there is no header */
		break;
	    /* last header */
	}
	else if (!(w3m_dump & DUMP_HEAD)) {
	    if (lineBuf2) {
		Strcat(lineBuf2, tmp);
	    }
	    else {
		lineBuf2 = tmp;
	    }
	    c = UFgetc(uf);
	    UFundogetc(uf);
	    if (c == ' ' || c == '\t')
		/* header line is continued */
		continue;
	    lineBuf2 = decodeMIME(lineBuf2, &mime_charset);
	    lineBuf2 = convertLine(NULL, lineBuf2, RAW_MODE,
				   mime_charset ? &mime_charset : &charset,
				   mime_charset ? mime_charset
				   : DocumentCharset);
	    /* separated with line and stored */
	    tmp = Strnew_size(lineBuf2->length);
	    for (p = lineBuf2->ptr; *p; p = q) {
		for (q = p; *q && *q != '\r' && *q != '\n'; q++) ;
		lineBuf2 = checkType(Strnew_charp_n(p, q - p), &propBuffer,
				     NULL);
		Strcat(tmp, lineBuf2);
		if (thru)
		    addnewline(newBuf, lineBuf2->ptr, propBuffer, NULL,
			       lineBuf2->length, FOLD_BUFFER_WIDTH, -1);
		for (; *q && (*q == '\r' || *q == '\n'); q++) ;
	    }
#ifdef USE_IMAGE
	    if (thru && activeImage && displayImage) {
		Str src = NULL;
		if (!strncasecmp(tmp->ptr, "X-Image-URL:", 12)) {
		    tmpf = &tmp->ptr[12];
		    SKIP_BLANKS(tmpf);
		    src = Strnew_m_charp("<img src=\"", html_quote(tmpf),
					 "\" alt=\"X-Image-URL\">", NULL);
		}
#ifdef USE_XFACE
		else if (!strncasecmp(tmp->ptr, "X-Face:", 7)) {
		    tmpf = xface2xpm(&tmp->ptr[7]);
		    if (tmpf)
			src = Strnew_m_charp("<img src=\"file:",
					     html_quote(tmpf),
					     "\" alt=\"X-Face\"",
					     " width=48 height=48>", NULL);
		}
#endif
		if (src) {
		    URLFile f;
		    Line *l;
#ifdef USE_M17N
		    wc_ces old_charset = newBuf->document_charset;
#endif
		    init_stream(&f, SCM_LOCAL, newStrStream(src));
		    loadHTMLstream(&f, newBuf, NULL, TRUE);
		    for (l = newBuf->lastLine; l && l->real_linenumber;
			 l = l->prev)
			l->real_linenumber = 0;
#ifdef USE_M17N
		    newBuf->document_charset = old_charset;
#endif
		}
	    }
#endif
	    lineBuf2 = tmp;
	}
	else {
	    lineBuf2 = tmp;
	}
	if ((uf->scheme == SCM_HTTP
#ifdef USE_SSL
	     || uf->scheme == SCM_HTTPS
#endif				/* USE_SSL */
	    ) && http_response_code == -1) {
	    p = lineBuf2->ptr;
	    while (*p && !IS_SPACE(*p))
		p++;
	    while (*p && IS_SPACE(*p))
		p++;
	    http_response_code = atoi(p);
	    if (fmInitialized) {
		message(lineBuf2->ptr, 0, 0);
		refresh();
	    }
	}
	if (!strncasecmp(lineBuf2->ptr, "content-transfer-encoding:", 26)) {
	    p = lineBuf2->ptr + 26;
	    while (IS_SPACE(*p))
		p++;
	    if (!strncasecmp(p, "base64", 6))
		uf->encoding = ENC_BASE64;
	    else if (!strncasecmp(p, "quoted-printable", 16))
		uf->encoding = ENC_QUOTE;
	    else if (!strncasecmp(p, "uuencode", 8) ||
		     !strncasecmp(p, "x-uuencode", 10))
		uf->encoding = ENC_UUENCODE;
	    else
		uf->encoding = ENC_7BIT;
	}
	else if (!strncasecmp(lineBuf2->ptr, "content-encoding:", 17)) {
	    struct compression_decoder *d;
	    p = lineBuf2->ptr + 17;
	    while (IS_SPACE(*p))
		p++;
	    uf->compression = CMP_NOCOMPRESS;
	    for (d = compression_decoders; d->type != CMP_NOCOMPRESS; d++) {
		char **e;
		for (e = d->encodings; *e != NULL; e++) {
		    if (strncasecmp(p, *e, strlen(*e)) == 0) {
			uf->compression = d->type;
			break;
		    }
		}
		if (uf->compression != CMP_NOCOMPRESS)
		    break;
	    }
	    uf->content_encoding = uf->compression;
	}
#ifdef USE_COOKIE
	else if (use_cookie && accept_cookie &&
		 pu && check_cookie_accept_domain(pu->host) &&
		 (!strncasecmp(lineBuf2->ptr, "Set-Cookie:", 11) ||
		  !strncasecmp(lineBuf2->ptr, "Set-Cookie2:", 12))) {
	    Str name = Strnew(), value = Strnew(), domain = NULL, path = NULL,
		comment = NULL, commentURL = NULL, port = NULL, tmp2;
	    int version, quoted, flag = 0;
	    time_t expires = (time_t) - 1;

	    q = NULL;
	    if (lineBuf2->ptr[10] == '2') {
		p = lineBuf2->ptr + 12;
		version = 1;
	    }
	    else {
		p = lineBuf2->ptr + 11;
		version = 0;
	    }
#ifdef DEBUG
	    fprintf(stderr, "Set-Cookie: [%s]\n", p);
#endif				/* DEBUG */
	    SKIP_BLANKS(p);
	    while (*p != '=' && !IS_ENDT(*p))
		Strcat_char(name, *(p++));
	    Strremovetrailingspaces(name);
	    if (*p == '=') {
		p++;
		SKIP_BLANKS(p);
		quoted = 0;
		while (!IS_ENDL(*p) && (quoted || *p != ';')) {
		    if (!IS_SPACE(*p))
			q = p;
		    if (*p == '"')
			quoted = (quoted) ? 0 : 1;
		    Strcat_char(value, *(p++));
		}
		if (q)
		    Strshrink(value, p - q - 1);
	    }
	    while (*p == ';') {
		p++;
		SKIP_BLANKS(p);
		if (matchattr(p, "expires", 7, &tmp2)) {
		    /* version 0 */
		    expires = mymktime(tmp2->ptr);
		}
		else if (matchattr(p, "max-age", 7, &tmp2)) {
		    /* XXX Is there any problem with max-age=0? (RFC 2109 ss. 4.2.1, 4.2.2 */
		    expires = time(NULL) + atol(tmp2->ptr);
		}
		else if (matchattr(p, "domain", 6, &tmp2)) {
		    domain = tmp2;
		}
		else if (matchattr(p, "path", 4, &tmp2)) {
		    path = tmp2;
		}
		else if (matchattr(p, "secure", 6, NULL)) {
		    flag |= COO_SECURE;
		}
		else if (matchattr(p, "comment", 7, &tmp2)) {
		    comment = tmp2;
		}
		else if (matchattr(p, "version", 7, &tmp2)) {
		    version = atoi(tmp2->ptr);
		}
		else if (matchattr(p, "port", 4, &tmp2)) {
		    /* version 1, Set-Cookie2 */
		    port = tmp2;
		}
		else if (matchattr(p, "commentURL", 10, &tmp2)) {
		    /* version 1, Set-Cookie2 */
		    commentURL = tmp2;
		}
		else if (matchattr(p, "discard", 7, NULL)) {
		    /* version 1, Set-Cookie2 */
		    flag |= COO_DISCARD;
		}
		quoted = 0;
		while (!IS_ENDL(*p) && (quoted || *p != ';')) {
		    if (*p == '"')
			quoted = (quoted) ? 0 : 1;
		    p++;
		}
	    }
	    if (pu && name->length > 0) {
		int err;
		if (show_cookie) {
		    if (flag & COO_SECURE)
		        disp_message_nsec("Received a secured cookie", FALSE, 1,
				      TRUE, FALSE);
		    else
		        disp_message_nsec(Sprintf("Received cookie: %s=%s",
					      name->ptr, value->ptr)->ptr,
				      FALSE, 1, TRUE, FALSE);
		}
		err =
		    add_cookie(pu, name, value, expires, domain, path, flag,
			       comment, version, port, commentURL);
		if (err) {
		    char *ans = (accept_bad_cookie == ACCEPT_BAD_COOKIE_ACCEPT)
			? "y" : NULL;
		    if (fmInitialized && (err & COO_OVERRIDE_OK) &&
			accept_bad_cookie == ACCEPT_BAD_COOKIE_ASK) {
			Str msg = Sprintf("Accept bad cookie from %s for %s?",
					  pu->host,
					  ((domain && domain->ptr)
					   ? domain->ptr : "<localdomain>"));
			if (msg->length > COLS - 10)
			    Strshrink(msg, msg->length - (COLS - 10));
			Strcat_charp(msg, " (y/n)");
			ans = inputAnswer(msg->ptr);
		    }
		    if (ans == NULL || TOLOWER(*ans) != 'y' ||
			(err =
			 add_cookie(pu, name, value, expires, domain, path,
				    flag | COO_OVERRIDE, comment, version,
				    port, commentURL))) {
			err = (err & ~COO_OVERRIDE_OK) - 1;
			if (err >= 0 && err < COO_EMAX)
			    emsg = Sprintf("This cookie was rejected "
					   "to prevent security violation. [%s]",
					   violations[err])->ptr;
			else
			    emsg =
				"This cookie was rejected to prevent security violation.";
			record_err_message(emsg);
			if (show_cookie)
			    disp_message_nsec(emsg, FALSE, 1, TRUE, FALSE);
		    }
		    else
			if (show_cookie)
			    disp_message_nsec(Sprintf
					  ("Accepting invalid cookie: %s=%s",
					   name->ptr, value->ptr)->ptr, FALSE,
					  1, TRUE, FALSE);
		}
	    }
	}
#endif				/* USE_COOKIE */
	else if (!strncasecmp(lineBuf2->ptr, "w3m-control:", 12) &&
		 uf->scheme == SCM_LOCAL_CGI) {
	    Str funcname = Strnew();
	    int f;

	    p = lineBuf2->ptr + 12;
	    SKIP_BLANKS(p);
	    while (*p && !IS_SPACE(*p))
		Strcat_char(funcname, *(p++));
	    SKIP_BLANKS(p);
	    f = getFuncList(funcname->ptr);
	    if (f >= 0) {
		tmp = Strnew_charp(p);
		Strchop(tmp);
		pushEvent(f, tmp->ptr);
	    }
	}
	if (headerlist)
	    pushText(headerlist, lineBuf2->ptr);
	Strfree(lineBuf2);
	lineBuf2 = NULL;
    }
    if (thru)
	addnewline(newBuf, "", propBuffer, NULL, 0, -1, -1);
    if (src)
	fclose(src);
}

char *
checkHeader(Buffer *buf, char *field)
{
    int len;
    TextListItem *i;
    char *p;

    if (buf == NULL || field == NULL || buf->document_header == NULL)
	return NULL;
    len = strlen(field);
    for (i = buf->document_header->first; i != NULL; i = i->next) {
	if (!strncasecmp(i->ptr, field, len)) {
	    p = i->ptr + len;
	    return remove_space(p);
	}
    }
    return NULL;
}

char *
checkContentType(Buffer *buf)
{
    char *p;
    Str r;
    p = checkHeader(buf, "Content-Type:");
    if (p == NULL)
	return NULL;
    r = Strnew();
    while (*p && *p != ';' && !IS_SPACE(*p))
	Strcat_char(r, *p++);
#ifdef USE_M17N
    if ((p = strcasestr(p, "charset")) != NULL) {
	p += 7;
	SKIP_BLANKS(p);
	if (*p == '=') {
	    p++;
	    SKIP_BLANKS(p);
	    if (*p == '"')
		p++;
	    content_charset = wc_guess_charset(p, 0);
	}
    }
#endif
    return r->ptr;
}

struct auth_param {
    char *name;
    Str val;
};

struct http_auth {
    int pri;
    char *scheme;
    struct auth_param *param;
    Str (*cred) (struct http_auth * ha, Str uname, Str pw, ParsedURL *pu,
		 HRequest *hr, FormList *request);
};

enum {
    AUTHCHR_NUL,
    AUTHCHR_SEP,
    AUTHCHR_TOKEN,
};

static int
skip_auth_token(char **pp)
{
    char *p;
    int first = AUTHCHR_NUL, typ;

    for (p = *pp ;; ++p) {
	switch (*p) {
	case '\0':
	    goto endoftoken;
	default:
	    if ((unsigned char)*p > 037) {
		typ = AUTHCHR_TOKEN;
		break;
	    }
	    /* thru */
	case '\177':
	case '[':
	case ']':
	case '(':
	case ')':
	case '<':
	case '>':
	case '@':
	case ';':
	case ':':
	case '\\':
	case '"':
	case '/':
	case '?':
	case '=':
	case ' ':
	case '\t':
	case ',':
	    typ = AUTHCHR_SEP;
	    break;
	}

	if (!first)
	    first = typ;
	else if (first != typ)
	    break;
    }
endoftoken:
    *pp = p;
    return first;
}

static Str
extract_auth_val(char **q)
{
    unsigned char *qq = *(unsigned char **)q;
    int quoted = 0;
    Str val = Strnew();

    SKIP_BLANKS(qq);
    if (*qq == '"') {
	quoted = TRUE;
	Strcat_char(val, *qq++);
    }
    while (*qq != '\0') {
	if (quoted && *qq == '"') {
	    Strcat_char(val, *qq++);
	    break;
	}
	if (!quoted) {
	    switch (*qq) {
	    case '[':
	    case ']':
	    case '(':
	    case ')':
	    case '<':
	    case '>':
	    case '@':
	    case ';':
	    case ':':
	    case '\\':
	    case '"':
	    case '/':
	    case '?':
	    case '=':
	    case ' ':
	    case '\t':
		qq++;
	    case ',':
		goto end_token;
	    default:
		if (*qq <= 037 || *qq == 0177) {
		    qq++;
		    goto end_token;
		}
	    }
	}
	else if (quoted && *qq == '\\')
	    Strcat_char(val, *qq++);
	Strcat_char(val, *qq++);
    }
  end_token:
    *q = (char *)qq;
    return val;
}

static Str
qstr_unquote(Str s)
{
    char *p;

    if (s == NULL)
	return NULL;
    p = s->ptr;
    if (*p == '"') {
	Str tmp = Strnew();
	for (p++; *p != '\0'; p++) {
	    if (*p == '\\')
		p++;
	    Strcat_char(tmp, *p);
	}
	if (Strlastchar(tmp) == '"')
	    Strshrink(tmp, 1);
	return tmp;
    }
    else
	return s;
}

static char *
extract_auth_param(char *q, struct auth_param *auth)
{
    struct auth_param *ap;
    char *p;

    for (ap = auth; ap->name != NULL; ap++) {
	ap->val = NULL;
    }

    while (*q != '\0') {
	SKIP_BLANKS(q);
	for (ap = auth; ap->name != NULL; ap++) {
	    size_t len;

	    len = strlen(ap->name);
	    if (strncasecmp(q, ap->name, len) == 0 &&
		(IS_SPACE(q[len]) || q[len] == '=')) {
		p = q + len;
		SKIP_BLANKS(p);
		if (*p != '=')
		    return q;
		q = p + 1;
		ap->val = extract_auth_val(&q);
		break;
	    }
	}
	if (ap->name == NULL) {
	    /* skip unknown param */
	    int token_type;
	    p = q;
	    if ((token_type = skip_auth_token(&q)) == AUTHCHR_TOKEN &&
		(IS_SPACE(*q) || *q == '=')) {
		SKIP_BLANKS(q);
		if (*q != '=')
		    return p;
		q++;
		extract_auth_val(&q);
	    }
	    else
		return p;
	}
	if (*q != '\0') {
	    SKIP_BLANKS(q);
	    if (*q == ',')
		q++;
	    else
		break;
	}
    }
    return q;
}

static Str
get_auth_param(struct auth_param *auth, char *name)
{
    struct auth_param *ap;
    for (ap = auth; ap->name != NULL; ap++) {
	if (strcasecmp(name, ap->name) == 0)
	    return ap->val;
    }
    return NULL;
}

static Str
AuthBasicCred(struct http_auth *ha, Str uname, Str pw, ParsedURL *pu,
	      HRequest *hr, FormList *request)
{
    Str s = Strdup(uname);
    Strcat_char(s, ':');
    Strcat(s, pw);
    return Strnew_m_charp("Basic ", encodeB(s->ptr)->ptr, NULL);
}

#ifdef USE_DIGEST_AUTH
#include <openssl/md5.h>

/* RFC2617: 3.2.2 The Authorization Request Header
 * 
 * credentials      = "Digest" digest-response
 * digest-response  = 1#( username | realm | nonce | digest-uri
 *                    | response | [ algorithm ] | [cnonce] |
 *                     [opaque] | [message-qop] |
 *                         [nonce-count]  | [auth-param] )
 *
 * username         = "username" "=" username-value
 * username-value   = quoted-string
 * digest-uri       = "uri" "=" digest-uri-value
 * digest-uri-value = request-uri   ; As specified by HTTP/1.1
 * message-qop      = "qop" "=" qop-value
 * cnonce           = "cnonce" "=" cnonce-value
 * cnonce-value     = nonce-value
 * nonce-count      = "nc" "=" nc-value
 * nc-value         = 8LHEX
 * response         = "response" "=" request-digest
 * request-digest = <"> 32LHEX <">
 * LHEX             =  "0" | "1" | "2" | "3" |
 *                     "4" | "5" | "6" | "7" |
 *                     "8" | "9" | "a" | "b" |
 *                     "c" | "d" | "e" | "f"
 */

static Str
digest_hex(unsigned char *p)
{
    char *h = "0123456789abcdef";
    Str tmp = Strnew_size(MD5_DIGEST_LENGTH * 2 + 1);
    int i;
    for (i = 0; i < MD5_DIGEST_LENGTH; i++, p++) {
	Strcat_char(tmp, h[(*p >> 4) & 0x0f]);
	Strcat_char(tmp, h[*p & 0x0f]);
    }
    return tmp;
}

enum {
    QOP_NONE,
    QOP_AUTH,
    QOP_AUTH_INT,
};

static Str
AuthDigestCred(struct http_auth *ha, Str uname, Str pw, ParsedURL *pu,
	       HRequest *hr, FormList *request)
{
    Str tmp, a1buf, a2buf, rd, s;
    unsigned char md5[MD5_DIGEST_LENGTH + 1];
    Str uri = HTTPrequestURI(pu, hr);
    char nc[] = "00000001";

    Str algorithm = qstr_unquote(get_auth_param(ha->param, "algorithm"));
    Str nonce = qstr_unquote(get_auth_param(ha->param, "nonce"));
    Str cnonce /* = qstr_unquote(get_auth_param(ha->param, "cnonce")) */;
    /* cnonce is what client should generate. */
    Str qop = qstr_unquote(get_auth_param(ha->param, "qop"));

    static union {
	int r[4];
	unsigned char s[sizeof(int) * 4];
    } cnonce_seed;
    int qop_i = QOP_NONE;

    cnonce_seed.r[0] = rand();
    cnonce_seed.r[1] = rand();
    cnonce_seed.r[2] = rand();
    MD5(cnonce_seed.s, sizeof(cnonce_seed.s), md5);
    cnonce = digest_hex(md5);
    cnonce_seed.r[3]++;

    if (qop) {
	char *p;
	size_t i;

	p = qop->ptr;
	SKIP_BLANKS(p);

	for (;;) {
	    if ((i = strcspn(p, " \t,")) > 0) {
		if (i == sizeof("auth-int") - sizeof("") && !strncasecmp(p, "auth-int", i)) {
		    if (qop_i < QOP_AUTH_INT)
			qop_i = QOP_AUTH_INT;
		}
		else if (i == sizeof("auth") - sizeof("") && !strncasecmp(p, "auth", i)) {
		    if (qop_i < QOP_AUTH)
			qop_i = QOP_AUTH;
		}
	    }

	    if (p[i]) {
		p += i + 1;
		SKIP_BLANKS(p);
	    }
	    else
		break;
	}
    }

    /* A1 = unq(username-value) ":" unq(realm-value) ":" passwd */
    tmp = Strnew_m_charp(uname->ptr, ":",
			 qstr_unquote(get_auth_param(ha->param, "realm"))->ptr,
			 ":", pw->ptr, NULL);
    MD5(tmp->ptr, strlen(tmp->ptr), md5);
    a1buf = digest_hex(md5);

    if (algorithm) {
	if (strcasecmp(algorithm->ptr, "MD5-sess") == 0) {
	    /* A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd)
	     *      ":" unq(nonce-value) ":" unq(cnonce-value)
	     */
	    if (nonce == NULL)
		return NULL;
	    tmp = Strnew_m_charp(a1buf->ptr, ":",
				 qstr_unquote(nonce)->ptr,
				 ":", qstr_unquote(cnonce)->ptr, NULL);
	    MD5(tmp->ptr, strlen(tmp->ptr), md5);
	    a1buf = digest_hex(md5);
	}
	else if (strcasecmp(algorithm->ptr, "MD5") == 0)
	    /* ok default */
	    ;
	else
	    /* unknown algorithm */
	    return NULL;
    }

    /* A2 = Method ":" digest-uri-value */
    tmp = Strnew_m_charp(HTTPrequestMethod(hr)->ptr, ":", uri->ptr, NULL);
    if (qop_i == QOP_AUTH_INT) {
	/*  A2 = Method ":" digest-uri-value ":" H(entity-body) */
	if (request && request->body) {
	    if (request->method == FORM_METHOD_POST && request->enctype == FORM_ENCTYPE_MULTIPART) {
		FILE *fp = fopen(request->body, "r");
		if (fp != NULL) {
		    Str ebody;
		    ebody = Strfgetall(fp);
		    MD5(ebody->ptr, strlen(ebody->ptr), md5);
		}
		else {
		    MD5("", 0, md5);
		}
	    }
	    else {
		MD5(request->body, request->length, md5);
	    }
	}
	else {
	    MD5("", 0, md5);
	}
	Strcat_char(tmp, ':');
	Strcat(tmp, digest_hex(md5));
    }
    MD5(tmp->ptr, strlen(tmp->ptr), md5);
    a2buf = digest_hex(md5);

    if (qop_i >= QOP_AUTH) {
	/* request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
	 *                      ":" nc-value
	 *                      ":" unq(cnonce-value)
	 *                      ":" unq(qop-value)
	 *                      ":" H(A2)
	 *                      ) <">
	 */
	if (nonce == NULL)
	    return NULL;
	tmp = Strnew_m_charp(a1buf->ptr, ":", qstr_unquote(nonce)->ptr,
			     ":", nc,
			     ":", qstr_unquote(cnonce)->ptr,
			     ":", qop_i == QOP_AUTH ? "auth" : "auth-int",
			     ":", a2buf->ptr, NULL);
	MD5(tmp->ptr, strlen(tmp->ptr), md5);
	rd = digest_hex(md5);
    }
    else {
	/* compatibility with RFC 2069
	 * request_digest = KD(H(A1),  unq(nonce), H(A2))
	 */
	tmp = Strnew_m_charp(a1buf->ptr, ":",
			     qstr_unquote(get_auth_param(ha->param, "nonce"))->
			     ptr, ":", a2buf->ptr, NULL);
	MD5(tmp->ptr, strlen(tmp->ptr), md5);
	rd = digest_hex(md5);
    }

    /*
     * digest-response  = 1#( username | realm | nonce | digest-uri
     *                          | response | [ algorithm ] | [cnonce] |
     *                          [opaque] | [message-qop] |
     *                          [nonce-count]  | [auth-param] )
     */

    tmp = Strnew_m_charp("Digest username=\"", uname->ptr, "\"", NULL);
    Strcat_m_charp(tmp, ", realm=",
		   get_auth_param(ha->param, "realm")->ptr, NULL);
    Strcat_m_charp(tmp, ", nonce=",
		   get_auth_param(ha->param, "nonce")->ptr, NULL);
    Strcat_m_charp(tmp, ", uri=\"", uri->ptr, "\"", NULL);
    Strcat_m_charp(tmp, ", response=\"", rd->ptr, "\"", NULL);

    if (algorithm)
	Strcat_m_charp(tmp, ", algorithm=",
		       get_auth_param(ha->param, "algorithm")->ptr, NULL);

    if (cnonce)
	Strcat_m_charp(tmp, ", cnonce=\"", cnonce->ptr, "\"", NULL);

    if ((s = get_auth_param(ha->param, "opaque")) != NULL)
	Strcat_m_charp(tmp, ", opaque=", s->ptr, NULL);

    if (qop_i >= QOP_AUTH) {
	Strcat_m_charp(tmp, ", qop=",
		       qop_i == QOP_AUTH ? "auth" : "auth-int",
		       NULL);
	/* XXX how to count? */
	/* Since nonce is unique up to each *-Authenticate and w3m does not re-use *-Authenticate: headers,
	   nonce-count should be always "00000001". */
	Strcat_m_charp(tmp, ", nc=", nc, NULL);
    }

    return tmp;
}
#endif

/* *INDENT-OFF* */
struct auth_param none_auth_param[] = {
    {NULL, NULL}
};

struct auth_param basic_auth_param[] = {
    {"realm", NULL},
    {NULL, NULL}
};

#ifdef USE_DIGEST_AUTH
/* RFC2617: 3.2.1 The WWW-Authenticate Response Header
 * challenge        =  "Digest" digest-challenge
 * 
 * digest-challenge  = 1#( realm | [ domain ] | nonce |
 *                       [ opaque ] |[ stale ] | [ algorithm ] |
 *                        [ qop-options ] | [auth-param] )
 *
 * domain            = "domain" "=" <"> URI ( 1*SP URI ) <">
 * URI               = absoluteURI | abs_path
 * nonce             = "nonce" "=" nonce-value
 * nonce-value       = quoted-string
 * opaque            = "opaque" "=" quoted-string
 * stale             = "stale" "=" ( "true" | "false" )
 * algorithm         = "algorithm" "=" ( "MD5" | "MD5-sess" |
 *                        token )
 * qop-options       = "qop" "=" <"> 1#qop-value <">
 * qop-value         = "auth" | "auth-int" | token
 */
struct auth_param digest_auth_param[] = {
    {"realm", NULL},
    {"domain", NULL},
    {"nonce", NULL},
    {"opaque", NULL},
    {"stale", NULL},
    {"algorithm", NULL},
    {"qop", NULL},
    {NULL, NULL}
};
#endif
/* for RFC2617: HTTP Authentication */
struct http_auth www_auth[] = {
    { 1, "Basic ", basic_auth_param, AuthBasicCred },
#ifdef USE_DIGEST_AUTH
    { 10, "Digest ", digest_auth_param, AuthDigestCred },
#endif
    { 0, NULL, NULL, NULL,}
};
/* *INDENT-ON* */

static struct http_auth *
findAuthentication(struct http_auth *hauth, Buffer *buf, char *auth_field)
{
    struct http_auth *ha;
    int len = strlen(auth_field), slen;
    TextListItem *i;
    char *p0, *p;

    bzero(hauth, sizeof(struct http_auth));
    for (i = buf->document_header->first; i != NULL; i = i->next) {
	if (strncasecmp(i->ptr, auth_field, len) == 0) {
	    for (p = i->ptr + len; p != NULL && *p != '\0';) {
		SKIP_BLANKS(p);
		p0 = p;
		for (ha = &www_auth[0]; ha->scheme != NULL; ha++) {
		    slen = strlen(ha->scheme);
		    if (strncasecmp(p, ha->scheme, slen) == 0) {
			p += slen;
			SKIP_BLANKS(p);
			if (hauth->pri < ha->pri) {
			    *hauth = *ha;
			    p = extract_auth_param(p, hauth->param);
			    break;
			}
			else {
			    /* weak auth */
			    p = extract_auth_param(p, none_auth_param);
			}
		    }
		}
		if (p0 == p) {
		    /* all unknown auth failed */
		    int token_type;
		    if ((token_type = skip_auth_token(&p)) == AUTHCHR_TOKEN && IS_SPACE(*p)) {
			SKIP_BLANKS(p);
			p = extract_auth_param(p, none_auth_param);
		    }
		    else
			break;
		}
	    }
	}
    }
    return hauth->scheme ? hauth : NULL;
}

static void
getAuthCookie(struct http_auth *hauth, char *auth_header,
	      TextList *extra_header, ParsedURL *pu, HRequest *hr,
	      FormList *request,
	      volatile Str *uname, volatile Str *pwd)
{
    Str ss = NULL;
    Str tmp;
    TextListItem *i;
    int a_found;
    int auth_header_len = strlen(auth_header);
    char *realm = NULL;
    int proxy;

    if (hauth)
	realm = qstr_unquote(get_auth_param(hauth->param, "realm"))->ptr;

    if (!realm)
	return;

    a_found = FALSE;
    for (i = extra_header->first; i != NULL; i = i->next) {
	if (!strncasecmp(i->ptr, auth_header, auth_header_len)) {
	    a_found = TRUE;
	    break;
	}
    }
    proxy = !strncasecmp("Proxy-Authorization:", auth_header,
			 auth_header_len);
    if (a_found) {
	/* This means that *-Authenticate: header is received after
	 * Authorization: header is sent to the server. 
	 */
	if (fmInitialized) {
	    message("Wrong username or password", 0, 0);
	    refresh();
	}
	else
	    fprintf(stderr, "Wrong username or password\n");
	sleep(1);
	/* delete Authenticate: header from extra_header */
	delText(extra_header, i);
	invalidate_auth_user_passwd(pu, realm, *uname, *pwd, proxy);
    }
    *uname = NULL;
    *pwd = NULL;

    if (!a_found && find_auth_user_passwd(pu, realm, (Str*)uname, (Str*)pwd, 
					  proxy)) {
	/* found username & password in passwd file */ ;
    }
    else {
	if (QuietMessage)
	    return;
	/* input username and password */
	sleep(2);
	if (fmInitialized) {
	    char *pp;
	    term_raw();
	    /* FIXME: gettextize? */
	    if ((pp = inputStr(Sprintf("Username for %s: ", realm)->ptr,
			       NULL)) == NULL)
		return;
	    *uname = Str_conv_to_system(Strnew_charp(pp));
	    if ((pp = inputLine(Sprintf("Password for %s: ", realm)->ptr, NULL,
				IN_PASSWORD)) == NULL) {
		*uname = NULL;
		return;
	    }
	    *pwd = Str_conv_to_system(Strnew_charp(pp));
	    term_cbreak();
	}
	else {
	    /*
	     * If post file is specified as '-', stdin is closed at this
	     * point.
	     * In this case, w3m cannot read username from stdin.
	     * So exit with error message.
	     * (This is same behavior as lwp-request.)
	     */
	    if (feof(stdin) || ferror(stdin)) {
		/* FIXME: gettextize? */
		fprintf(stderr, "w3m: Authorization required for %s\n",
			realm);
		exit(1);
	    }
	    
	    /* FIXME: gettextize? */
	    printf(proxy ? "Proxy Username for %s: " : "Username for %s: ",
		   realm);
	    fflush(stdout);
	    *uname = Strfgets(stdin);
	    Strchop(*uname);
#ifdef HAVE_GETPASSPHRASE
	    *pwd = Strnew_charp((char *)
				getpassphrase(proxy ? "Proxy Password: " :
					      "Password: "));
#else
#ifndef __MINGW32_VERSION
	    *pwd = Strnew_charp((char *)
				getpass(proxy ? "Proxy Password: " :
					"Password: "));
#else
	    term_raw();
	    *pwd = Strnew_charp((char *)
				inputLine(proxy ? "Proxy Password: " :
					  "Password: ", NULL, IN_PASSWORD));
	    term_cbreak();
#endif /* __MINGW32_VERSION */
#endif
	}
    }
    ss = hauth->cred(hauth, *uname, *pwd, pu, hr, request);
    if (ss) {
	tmp = Strnew_charp(auth_header);
	Strcat_m_charp(tmp, " ", ss->ptr, "\r\n", NULL);
	pushText(extra_header, tmp->ptr);
    }
    else {
	*uname = NULL;
	*pwd = NULL;
    }
    return;
}

static int
same_url_p(ParsedURL *pu1, ParsedURL *pu2)
{
    return (pu1->scheme == pu2->scheme && pu1->port == pu2->port &&
	    (pu1->host ? pu2->host ? !strcasecmp(pu1->host, pu2->host) : 0 : 1)
	    && (pu1->file ? pu2->
		file ? !strcmp(pu1->file, pu2->file) : 0 : 1));
}

static int
checkRedirection(ParsedURL *pu)
{
    static ParsedURL *puv = NULL;
    static int nredir = 0;
    static int nredir_size = 0;
    Str tmp;

    if (pu == NULL) {
	nredir = 0;
	nredir_size = 0;
	puv = NULL;
	return TRUE;
    }
    if (nredir >= FollowRedirection) {
	/* FIXME: gettextize? */
	tmp = Sprintf("Number of redirections exceeded %d at %s",
		      FollowRedirection, parsedURL2Str(pu)->ptr);
	disp_err_message(tmp->ptr, FALSE);
	return FALSE;
    }
    else if (nredir_size > 0 &&
	     (same_url_p(pu, &puv[(nredir - 1) % nredir_size]) ||
	      (!(nredir % 2)
	       && same_url_p(pu, &puv[(nredir / 2) % nredir_size])))) {
	/* FIXME: gettextize? */
	tmp = Sprintf("Redirection loop detected (%s)",
		      parsedURL2Str(pu)->ptr);
	disp_err_message(tmp->ptr, FALSE);
	return FALSE;
    }
    if (!puv) {
	nredir_size = FollowRedirection / 2 + 1;
	puv = New_N(ParsedURL, nredir_size);
	memset(puv, 0, sizeof(ParsedURL) * nredir_size);
    }
    copyParsedURL(&puv[nredir % nredir_size], pu);
    nredir++;
    return TRUE;
}

Str
getLinkNumberStr(int correction)
{
    return Sprintf("[%d]", cur_hseq + correction);
}

/* 
 * loadGeneralFile: load file to buffer
 */
Buffer *
loadGeneralFile(char *path, ParsedURL *volatile current, char *referer,
		int flag, FormList *volatile request)
{
    URLFile f, *volatile of = NULL;
    ParsedURL pu;
    Buffer *b = NULL, *(*volatile proc)() = loadBuffer;
    char *volatile tpath;
    char *volatile t = "text/plain", *p, *volatile real_type = NULL;
    Buffer *volatile t_buf = NULL;
    int volatile searchHeader = SearchHeader;
    int volatile searchHeader_through = TRUE;
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
    TextList *extra_header = newTextList();
    volatile Str uname = NULL;
    volatile Str pwd = NULL;
    volatile Str realm = NULL;
    int volatile add_auth_cookie_flag;
    unsigned char status = HTST_NORMAL;
    URLOption url_option;
    Str tmp;
    Str volatile page = NULL;
#ifdef USE_M17N
    wc_ces charset = WC_CES_US_ASCII;
#endif
    HRequest hr;
    ParsedURL *volatile auth_pu;

    tpath = path;
    prevtrap = NULL;
    add_auth_cookie_flag = 0;

    checkRedirection(NULL);
  load_doc:
    TRAP_OFF;
    url_option.referer = referer;
    url_option.flag = flag;
    f = openURL(tpath, &pu, current, &url_option, request, extra_header, of,
		&hr, &status);
    of = NULL;
#ifdef USE_M17N
    content_charset = 0;
#endif
    if (f.stream == NULL) {
	switch (f.scheme) {
	case SCM_LOCAL:
	    {
		struct stat st;
		if (stat(pu.real_file, &st) < 0)
		    return NULL;
		if (S_ISDIR(st.st_mode)) {
		    if (UseExternalDirBuffer) {
			Str cmd = Sprintf("%s?dir=%s#current",
					  DirBufferCommand, pu.file);
			b = loadGeneralFile(cmd->ptr, NULL, NO_REFERER, 0,
					    NULL);
			if (b != NULL && b != NO_BUFFER) {
			    copyParsedURL(&b->currentURL, &pu);
			    b->filename = b->currentURL.real_file;
			}
			return b;
		    }
		    else {
			page = loadLocalDir(pu.real_file);
			t = "local:directory";
#ifdef USE_M17N
			charset = SystemCharset;
#endif
		    }
		}
	    }
	    break;
	case SCM_FTPDIR:
	    page = loadFTPDir(&pu, &charset);
	    t = "ftp:directory";
	    break;
#ifdef USE_NNTP
	case SCM_NEWS_GROUP:
	    page = loadNewsgroup(&pu, &charset);
	    t = "news:group";
	    break;
#endif
	case SCM_UNKNOWN:
#ifdef USE_EXTERNAL_URI_LOADER
	    tmp = searchURIMethods(&pu);
	    if (tmp != NULL) {
		b = loadGeneralFile(tmp->ptr, current, referer, flag, request);
		if (b != NULL && b != NO_BUFFER)
		    copyParsedURL(&b->currentURL, &pu);
		return b;
	    }
#endif
	    /* FIXME: gettextize? */
	    disp_err_message(Sprintf("Unknown URI: %s",
				     parsedURL2Str(&pu)->ptr)->ptr, FALSE);
	    break;
	}
	if (page && page->length > 0)
	    goto page_loaded;
	return NULL;
    }

    if (status == HTST_MISSING) {
	TRAP_OFF;
	UFclose(&f);
	return NULL;
    }

    /* openURL() succeeded */
    if (SETJMP(AbortLoading) != 0) {
	/* transfer interrupted */
	TRAP_OFF;
	if (b)
	    discardBuffer(b);
	UFclose(&f);
	return NULL;
    }

    b = NULL;
    if (f.is_cgi) {
	/* local CGI */
	searchHeader = TRUE;
	searchHeader_through = FALSE;
    }
    if (header_string)
	header_string = NULL;
    TRAP_ON;
    if (pu.scheme == SCM_HTTP ||
#ifdef USE_SSL
	pu.scheme == SCM_HTTPS ||
#endif				/* USE_SSL */
	((
#ifdef USE_GOPHER
	     (pu.scheme == SCM_GOPHER && non_null(GOPHER_proxy)) ||
#endif				/* USE_GOPHER */
	     (pu.scheme == SCM_FTP && non_null(FTP_proxy))
	 ) && !Do_not_use_proxy && !check_no_proxy(pu.host))) {

	if (fmInitialized) {
	    term_cbreak();
	    /* FIXME: gettextize? */
	    message(Sprintf("%s contacted. Waiting for reply...", pu.host)->
		    ptr, 0, 0);
	    refresh();
	}
	if (t_buf == NULL)
	    t_buf = newBuffer(INIT_BUFFER_WIDTH);
#if 0				/* USE_SSL */
	if (IStype(f.stream) == IST_SSL) {
	    Str s = ssl_get_certificate(f.stream, pu.host);
	    if (s == NULL)
		return NULL;
	    else
		t_buf->ssl_certificate = s->ptr;
	}
#endif
	readHeader(&f, t_buf, FALSE, &pu);
	if (((http_response_code >= 301 && http_response_code <= 303)
	     || http_response_code == 307)
	    && (p = checkHeader(t_buf, "Location:")) != NULL
	    && checkRedirection(&pu)) {
	    /* document moved */
	    /* 301: Moved Permanently */
	    /* 302: Found */
	    /* 303: See Other */
	    /* 307: Temporary Redirect (HTTP/1.1) */
	    tpath = url_quote_conv(p, DocumentCharset);
	    request = NULL;
	    UFclose(&f);
	    current = New(ParsedURL);
	    copyParsedURL(current, &pu);
	    t_buf = newBuffer(INIT_BUFFER_WIDTH);
	    t_buf->bufferprop |= BP_REDIRECTED;
	    status = HTST_NORMAL;
	    goto load_doc;
	}
	t = checkContentType(t_buf);
	if (t == NULL && pu.file != NULL) {
	    if (!((http_response_code >= 400 && http_response_code <= 407) ||
		  (http_response_code >= 500 && http_response_code <= 505)))
		t = guessContentType(pu.file);
	}
	if (t == NULL)
	    t = "text/plain";
	if (add_auth_cookie_flag && realm && uname && pwd) {
	    /* If authorization is required and passed */
	    add_auth_user_passwd(&pu, qstr_unquote(realm)->ptr, uname, pwd, 
				  0);
	    add_auth_cookie_flag = 0;
	}
	if ((p = checkHeader(t_buf, "WWW-Authenticate:")) != NULL &&
	    http_response_code == 401) {
	    /* Authentication needed */
	    struct http_auth hauth;
	    if (findAuthentication(&hauth, t_buf, "WWW-Authenticate:") != NULL
		&& (realm = get_auth_param(hauth.param, "realm")) != NULL) {
		auth_pu = &pu;
		getAuthCookie(&hauth, "Authorization:", extra_header,
			      auth_pu, &hr, request, &uname, &pwd);
		if (uname == NULL) {
		    /* abort */
		    TRAP_OFF;
		    goto page_loaded;
		}
		UFclose(&f);
		add_auth_cookie_flag = 1;
		status = HTST_NORMAL;
		goto load_doc;
	    }
	}
	if ((p = checkHeader(t_buf, "Proxy-Authenticate:")) != NULL &&
	    http_response_code == 407) {
	    /* Authentication needed */
	    struct http_auth hauth;
	    if (findAuthentication(&hauth, t_buf, "Proxy-Authenticate:")
		!= NULL
		&& (realm = get_auth_param(hauth.param, "realm")) != NULL) {
		auth_pu = schemeToProxy(pu.scheme);
		getAuthCookie(&hauth, "Proxy-Authorization:",
			      extra_header, auth_pu, &hr, request, 
			      &uname, &pwd);
		if (uname == NULL) {
		    /* abort */
		    TRAP_OFF;
		    goto page_loaded;
		}
		UFclose(&f);
		add_auth_cookie_flag = 1;
		status = HTST_NORMAL;
		add_auth_user_passwd(auth_pu, qstr_unquote(realm)->ptr, uname, pwd, 1);
		goto load_doc;
	    }
	}
	/* XXX: RFC2617 3.2.3 Authentication-Info: ? */

	if (status == HTST_CONNECT) {
	    of = &f;
	    goto load_doc;
	}

	f.modtime = mymktime(checkHeader(t_buf, "Last-Modified:"));
    }
#ifdef USE_NNTP
    else if (pu.scheme == SCM_NEWS || pu.scheme == SCM_NNTP) {
	if (t_buf == NULL)
	    t_buf = newBuffer(INIT_BUFFER_WIDTH);
	readHeader(&f, t_buf, TRUE, &pu);
	t = checkContentType(t_buf);
	if (t == NULL)
	    t = "text/plain";
    }
#endif				/* USE_NNTP */
#ifdef USE_GOPHER
    else if (pu.scheme == SCM_GOPHER) {
	switch (*pu.file) {
	case '0':
	    t = "text/plain";
	    break;
	case '1':
	case 'm':
	    page = loadGopherDir(&f, &pu, &charset);
	    t = "gopher:directory";
	    TRAP_OFF;
	    goto page_loaded;
	case 's':
	    t = "audio/basic";
	    break;
	case 'g':
	    t = "image/gif";
	    break;
	case 'h':
	    t = "text/html";
	    break;
	}
    }
#endif				/* USE_GOPHER */
    else if (pu.scheme == SCM_FTP) {
	check_compression(path, &f);
	if (f.compression != CMP_NOCOMPRESS) {
	    char *t1 = uncompressed_file_type(pu.file, NULL);
	    real_type = f.guess_type;
#if 0
	    if (t1 && strncasecmp(t1, "application/", 12) == 0) {
		f.compression = CMP_NOCOMPRESS;
		t = real_type;
	    }
	    else
#endif
	    if (t1)
		t = t1;
	    else
		t = real_type;
	}
	else {
	    real_type = guessContentType(pu.file);
	    if (real_type == NULL)
		real_type = "text/plain";
	    t = real_type;
	}
#if 0
	if (!strncasecmp(t, "application/", 12)) {
	    char *tmpf = tmpfname(TMPF_DFL, NULL)->ptr;
	    current_content_length = 0;
	    if (save2tmp(f, tmpf) < 0)
		UFclose(&f);
	    else {
		UFclose(&f);
		TRAP_OFF;
		doFileMove(tmpf, guess_save_name(t_buf, pu.file));
	    }
	    return NO_BUFFER;
	}
#endif
    }
    else if (pu.scheme == SCM_DATA) {
	t = f.guess_type;
    }
    else if (searchHeader) {
	searchHeader = SearchHeader = FALSE;
	if (t_buf == NULL)
	    t_buf = newBuffer(INIT_BUFFER_WIDTH);
	readHeader(&f, t_buf, searchHeader_through, &pu);
	if (f.is_cgi && (p = checkHeader(t_buf, "Location:")) != NULL &&
	    checkRedirection(&pu)) {
	    /* document moved */
	    tpath = url_quote_conv(remove_space(p), DocumentCharset);
	    request = NULL;
	    UFclose(&f);
	    add_auth_cookie_flag = 0;
	    current = New(ParsedURL);
	    copyParsedURL(current, &pu);
	    t_buf = newBuffer(INIT_BUFFER_WIDTH);
	    t_buf->bufferprop |= BP_REDIRECTED;
	    status = HTST_NORMAL;
	    goto load_doc;
	}
#ifdef AUTH_DEBUG
	if ((p = checkHeader(t_buf, "WWW-Authenticate:")) != NULL) {
	    /* Authentication needed */
	    struct http_auth hauth;
	    if (findAuthentication(&hauth, t_buf, "WWW-Authenticate:") != NULL
		&& (realm = get_auth_param(hauth.param, "realm")) != NULL) {
		auth_pu = &pu;
		getAuthCookie(&hauth, "Authorization:", extra_header,
			      auth_pu, &hr, request, &uname, &pwd);
		if (uname == NULL) {
		    /* abort */
		    TRAP_OFF;
		    goto page_loaded;
		}
		UFclose(&f);
		add_auth_cookie_flag = 1;
		status = HTST_NORMAL;
		goto load_doc;
	    }
	}
#endif /* defined(AUTH_DEBUG) */
	t = checkContentType(t_buf);
	if (t == NULL)
	    t = "text/plain";
    }
    else if (DefaultType) {
	t = DefaultType;
	DefaultType = NULL;
    }
    else {
	t = guessContentType(pu.file);
	if (t == NULL)
	    t = "text/plain";
	real_type = t;
	if (f.guess_type)
	    t = f.guess_type;
    }

    /* XXX: can we use guess_type to give the type to loadHTMLstream
     *      to support default utf8 encoding for XHTML here? */
    f.guess_type = t;
    
  page_loaded:
    if (page) {
	FILE *src;
#ifdef USE_IMAGE
	if (image_source)
	    return NULL;
#endif
	tmp = tmpfname(TMPF_SRC, ".html");
	src = fopen(tmp->ptr, "w");
	if (src) {
	    Str s;
	    s = wc_Str_conv_strict(page, InnerCharset, charset);
	    Strfputs(s, src);
	    fclose(src);
	}
	if (do_download) {
	    char *file;
	    if (!src)
		return NULL;
	    file = guess_filename(pu.file);
#ifdef USE_GOPHER
	    if (f.scheme == SCM_GOPHER)
		file = Sprintf("%s.html", file)->ptr;
#endif
#ifdef USE_NNTP
	    if (f.scheme == SCM_NEWS_GROUP)
		file = Sprintf("%s.html", file)->ptr;
#endif
	    doFileMove(tmp->ptr, file);
	    return NO_BUFFER;
	}
	b = loadHTMLString(page);
	if (b) {
	    copyParsedURL(&b->currentURL, &pu);
	    b->real_scheme = pu.scheme;
	    b->real_type = t;
	    if (src)
		b->sourcefile = tmp->ptr;
#ifdef USE_M17N
	    b->document_charset = charset;
#endif
	}
	return b;
    }

    if (real_type == NULL)
	real_type = t;
    proc = loadBuffer;
#ifdef USE_IMAGE
    cur_baseURL = New(ParsedURL);
    copyParsedURL(cur_baseURL, &pu);
#endif

    current_content_length = 0;
    if ((p = checkHeader(t_buf, "Content-Length:")) != NULL)
	current_content_length = strtoclen(p);
    if (do_download) {
	/* download only */
	char *file;
	TRAP_OFF;
	if (DecodeCTE && IStype(f.stream) != IST_ENCODED)
	    f.stream = newEncodedStream(f.stream, f.encoding);
	if (pu.scheme == SCM_LOCAL) {
	    struct stat st;
	    if (PreserveTimestamp && !stat(pu.real_file, &st))
		f.modtime = st.st_mtime;
	    file = conv_from_system(guess_save_name(NULL, pu.real_file));
	}
	else
	    file = guess_save_name(t_buf, pu.file);
	if (doFileSave(f, file) == 0)
	    UFhalfclose(&f);
	else
	    UFclose(&f);
	return NO_BUFFER;
    }

    if ((f.content_encoding != CMP_NOCOMPRESS) && AutoUncompress
	&& !(w3m_dump & DUMP_EXTRA)) {
	uncompress_stream(&f, &pu.real_file);
    }
    else if (f.compression != CMP_NOCOMPRESS) {
	if (!(w3m_dump & DUMP_SOURCE) &&
	    (w3m_dump & ~DUMP_FRAME || is_text_type(t)
	     || searchExtViewer(t))) {
	    if (t_buf == NULL)
		t_buf = newBuffer(INIT_BUFFER_WIDTH);
	    uncompress_stream(&f, &t_buf->sourcefile);
	    uncompressed_file_type(pu.file, &f.ext);
	}
	else {
	    t = compress_application_type(f.compression);
	    f.compression = CMP_NOCOMPRESS;
	}
    }
#ifdef USE_IMAGE
    if (image_source) {
	Buffer *b = NULL;
	if (IStype(f.stream) != IST_ENCODED)
	    f.stream = newEncodedStream(f.stream, f.encoding);
	if (save2tmp(f, image_source) == 0) {
	    b = newBuffer(INIT_BUFFER_WIDTH);
	    b->sourcefile = image_source;
	    b->real_type = t;
	}
	UFclose(&f);
	TRAP_OFF;
	return b;
    }
#endif

    if (is_html_type(t))
	proc = loadHTMLBuffer;
    else if (is_plain_text_type(t))
	proc = loadBuffer;
#ifdef USE_IMAGE
    else if (activeImage && displayImage && !useExtImageViewer &&
	     !(w3m_dump & ~DUMP_FRAME) && !strncasecmp(t, "image/", 6))
	proc = loadImageBuffer;
#endif
    else if (w3m_backend) ;
    else if (!(w3m_dump & ~DUMP_FRAME) || is_dump_text_type(t)) {
	if (!do_download && doExternal(f,
				       pu.real_file ? pu.real_file : pu.file,
				       t, &b, t_buf)) {
	    if (b && b != NO_BUFFER) {
		b->real_scheme = f.scheme;
		b->real_type = real_type;
		if (b->currentURL.host == NULL && b->currentURL.file == NULL)
		    copyParsedURL(&b->currentURL, &pu);
	    }
	    UFclose(&f);
	    TRAP_OFF;
	    return b;
	}
	else {
	    TRAP_OFF;
	    if (pu.scheme == SCM_LOCAL) {
		UFclose(&f);
		_doFileCopy(pu.real_file,
			    conv_from_system(guess_save_name
					     (NULL, pu.real_file)), TRUE);
	    }
	    else {
		if (DecodeCTE && IStype(f.stream) != IST_ENCODED)
		    f.stream = newEncodedStream(f.stream, f.encoding);
		if (doFileSave(f, guess_save_name(t_buf, pu.file)) == 0)
		    UFhalfclose(&f);
		else
		    UFclose(&f);
	    }
	    return NO_BUFFER;
	}
    }
    else if (w3m_dump & DUMP_FRAME)
	return NULL;

    if (flag & RG_FRAME) {
	if (t_buf == NULL)
	    t_buf = newBuffer(INIT_BUFFER_WIDTH);
	t_buf->bufferprop |= BP_FRAME;
    }
#ifdef USE_SSL
    if (t_buf)
	t_buf->ssl_certificate = f.ssl_certificate;
#endif
    frame_source = flag & RG_FRAME_SRC;
    b = loadSomething(&f, pu.real_file ? pu.real_file : pu.file, proc, t_buf);
    UFclose(&f);
    frame_source = 0;
    if (b) {
	b->real_scheme = f.scheme;
	b->real_type = real_type;
	if (b->currentURL.host == NULL && b->currentURL.file == NULL)
	    copyParsedURL(&b->currentURL, &pu);
	if (is_html_type(t))
	    b->type = "text/html";
	else if (w3m_backend) {
	    Str s = Strnew_charp(t);
	    b->type = s->ptr;
	}
#ifdef USE_IMAGE
	else if (proc == loadImageBuffer)
	    b->type = "text/html";
#endif
	else
	    b->type = "text/plain";
	if (pu.label) {
	    if (proc == loadHTMLBuffer) {
		Anchor *a;
		a = searchURLLabel(b, pu.label);
		if (a != NULL) {
		    gotoLine(b, a->start.line);
		    if (label_topline)
			b->topLine = lineSkip(b, b->topLine,
					      b->currentLine->linenumber
					      - b->topLine->linenumber, FALSE);
		    b->pos = a->start.pos;
		    arrangeCursor(b);
		}
	    }
	    else {		/* plain text */
		int l = atoi(pu.label);
		gotoRealLine(b, l);
		b->pos = 0;
		arrangeCursor(b);
	    }
	}
    }
    if (header_string)
	header_string = NULL;
#ifdef USE_NNTP
    if (f.scheme == SCM_NNTP || f.scheme == SCM_NEWS)
	reAnchorNewsheader(b);
#endif
    preFormUpdateBuffer(b);
    TRAP_OFF;
    return b;
}

#define TAG_IS(s,tag,len)\
  (strncasecmp(s,tag,len)==0&&(s[len] == '>' || IS_SPACE((int)s[len])))

static char *
has_hidden_link(struct readbuffer *obuf, int cmd)
{
    Str line = obuf->line;
    struct link_stack *p;

    if (Strlastchar(line) != '>')
	return NULL;

    for (p = link_stack; p; p = p->next)
	if (p->cmd == cmd)
	    break;
    if (!p)
	return NULL;

    if (obuf->pos == p->pos)
	return line->ptr + p->offset;

    return NULL;
}

static void
push_link(int cmd, int offset, int pos)
{
    struct link_stack *p;
    p = New(struct link_stack);
    p->cmd = cmd;
    p->offset = offset;
    p->pos = pos;
    p->next = link_stack;
    link_stack = p;
}

static int
is_period_char(unsigned char *ch)
{
    switch (*ch) {
    case ',':
    case '.':
    case ':':
    case ';':
    case '?':
    case '!':
    case ')':
    case ']':
    case '}':
    case '>':
	return 1;
    default:
	return 0;
    }
}

static int
is_beginning_char(unsigned char *ch)
{
    switch (*ch) {
    case '(':
    case '[':
    case '{':
    case '`':
    case '<':
	return 1;
    default:
	return 0;
    }
}

static int
is_word_char(unsigned char *ch)
{
    Lineprop ctype = get_mctype(ch);

#ifdef USE_M17N
    if (ctype & (PC_CTRL | PC_KANJI | PC_UNKNOWN))
	return 0;
    if (ctype & (PC_WCHAR1 | PC_WCHAR2))
	return 1;
#else
    if (ctype == PC_CTRL)
	return 0;
#endif

    if (IS_ALNUM(*ch))
	return 1;

    switch (*ch) {
    case ',':
    case '.':
    case ':':
    case '\"':			/* " */
    case '\'':
    case '$':
    case '%':
    case '*':
    case '+':
    case '-':
    case '@':
    case '~':
    case '_':
	return 1;
    }
#ifdef USE_M17N
    if (*ch == NBSP_CODE)
	return 1;
#else
    if (*ch == TIMES_CODE || *ch == DIVIDE_CODE || *ch == ANSP_CODE)
	return 0;
    if (*ch >= AGRAVE_CODE || *ch == NBSP_CODE)
	return 1;
#endif
    return 0;
}

#ifdef USE_M17N
static int
is_combining_char(unsigned char *ch)
{
    Lineprop ctype = get_mctype(ch);

    if (ctype & PC_WCHAR2)
	return 1;
    return 0;
}
#endif

int
is_boundary(unsigned char *ch1, unsigned char *ch2)
{
    if (!*ch1 || !*ch2)
	return 1;

    if (*ch1 == ' ' && *ch2 == ' ')
	return 0;

    if (*ch1 != ' ' && is_period_char(ch2))
	return 0;

    if (*ch2 != ' ' && is_beginning_char(ch1))
	return 0;

#ifdef USE_M17N
    if (is_combining_char(ch2))
	return 0;
#endif
    if (is_word_char(ch1) && is_word_char(ch2))
	return 0;

    return 1;
}


static void
set_breakpoint(struct readbuffer *obuf, int tag_length)
{
    obuf->bp.len = obuf->line->length;
    obuf->bp.pos = obuf->pos;
    obuf->bp.tlen = tag_length;
    obuf->bp.flag = obuf->flag;
#ifdef FORMAT_NICE
    obuf->bp.flag &= ~RB_FILL;
#endif				/* FORMAT_NICE */
    obuf->bp.top_margin = obuf->top_margin;
    obuf->bp.bottom_margin = obuf->bottom_margin;

    if (!obuf->bp.init_flag)
	return;

    bcopy((void *)&obuf->anchor, (void *)&obuf->bp.anchor,
	  sizeof(obuf->anchor));
    obuf->bp.img_alt = obuf->img_alt;
    obuf->bp.in_bold = obuf->in_bold;
    obuf->bp.in_italic = obuf->in_italic;
    obuf->bp.in_under = obuf->in_under;
    obuf->bp.in_strike = obuf->in_strike;
    obuf->bp.in_ins = obuf->in_ins;
    obuf->bp.nobr_level = obuf->nobr_level;
    obuf->bp.prev_ctype = obuf->prev_ctype;
    obuf->bp.init_flag = 0;
}

static void
back_to_breakpoint(struct readbuffer *obuf)
{
    obuf->flag = obuf->bp.flag;
    bcopy((void *)&obuf->bp.anchor, (void *)&obuf->anchor,
	  sizeof(obuf->anchor));
    obuf->img_alt = obuf->bp.img_alt;
    obuf->in_bold = obuf->bp.in_bold;
    obuf->in_italic = obuf->bp.in_italic;
    obuf->in_under = obuf->bp.in_under;
    obuf->in_strike = obuf->bp.in_strike;
    obuf->in_ins = obuf->bp.in_ins;
    obuf->prev_ctype = obuf->bp.prev_ctype;
    obuf->pos = obuf->bp.pos;
    obuf->top_margin = obuf->bp.top_margin;
    obuf->bottom_margin = obuf->bp.bottom_margin;
    if (obuf->flag & RB_NOBR)
	obuf->nobr_level = obuf->bp.nobr_level;
}

static void
append_tags(struct readbuffer *obuf)
{
    int i;
    int len = obuf->line->length;
    int set_bp = 0;

    for (i = 0; i < obuf->tag_sp; i++) {
	switch (obuf->tag_stack[i]->cmd) {
	case HTML_A:
	case HTML_IMG_ALT:
	case HTML_B:
	case HTML_U:
	case HTML_I:
	case HTML_S:
	    push_link(obuf->tag_stack[i]->cmd, obuf->line->length, obuf->pos);
	    break;
	}
	Strcat_charp(obuf->line, obuf->tag_stack[i]->cmdname);
	switch (obuf->tag_stack[i]->cmd) {
	case HTML_NOBR:
	    if (obuf->nobr_level > 1)
		break;
	case HTML_WBR:
	    set_bp = 1;
	    break;
	}
    }
    obuf->tag_sp = 0;
    if (set_bp)
	set_breakpoint(obuf, obuf->line->length - len);
}

static void
push_tag(struct readbuffer *obuf, char *cmdname, int cmd)
{
    obuf->tag_stack[obuf->tag_sp] = New(struct cmdtable);
    obuf->tag_stack[obuf->tag_sp]->cmdname = allocStr(cmdname, -1);
    obuf->tag_stack[obuf->tag_sp]->cmd = cmd;
    obuf->tag_sp++;
    if (obuf->tag_sp >= TAG_STACK_SIZE || obuf->flag & (RB_SPECIAL & ~RB_NOBR))
	append_tags(obuf);
}

static void
push_nchars(struct readbuffer *obuf, int width,
	    char *str, int len, Lineprop mode)
{
    append_tags(obuf);
    Strcat_charp_n(obuf->line, str, len);
    obuf->pos += width;
    if (width > 0) {
	set_prevchar(obuf->prevchar, str, len);
	obuf->prev_ctype = mode;
    }
    obuf->flag |= RB_NFLUSHED;
}

#define push_charp(obuf, width, str, mode)\
push_nchars(obuf, width, str, strlen(str), mode)

#define push_str(obuf, width, str, mode)\
push_nchars(obuf, width, str->ptr, str->length, mode)

static void
check_breakpoint(struct readbuffer *obuf, int pre_mode, char *ch)
{
    int tlen, len = obuf->line->length;

    append_tags(obuf);
    if (pre_mode)
	return;
    tlen = obuf->line->length - len;
    if (tlen > 0
	|| is_boundary((unsigned char *)obuf->prevchar->ptr,
		       (unsigned char *)ch))
	set_breakpoint(obuf, tlen);
}

static void
push_char(struct readbuffer *obuf, int pre_mode, char ch)
{
    check_breakpoint(obuf, pre_mode, &ch);
    Strcat_char(obuf->line, ch);
    obuf->pos++;
    set_prevchar(obuf->prevchar, &ch, 1);
    if (ch != ' ')
	obuf->prev_ctype = PC_ASCII;
    obuf->flag |= RB_NFLUSHED;
}

#define PUSH(c) push_char(obuf, obuf->flag & RB_SPECIAL, c)

static void
push_spaces(struct readbuffer *obuf, int pre_mode, int width)
{
    int i;

    if (width <= 0)
	return;
    check_breakpoint(obuf, pre_mode, " ");
    for (i = 0; i < width; i++)
	Strcat_char(obuf->line, ' ');
    obuf->pos += width;
    set_space_to_prevchar(obuf->prevchar);
    obuf->flag |= RB_NFLUSHED;
}

static void
proc_mchar(struct readbuffer *obuf, int pre_mode,
	   int width, char **str, Lineprop mode)
{
    check_breakpoint(obuf, pre_mode, *str);
    obuf->pos += width;
    Strcat_charp_n(obuf->line, *str, get_mclen(*str));
    if (width > 0) {
	set_prevchar(obuf->prevchar, *str, 1);
	if (**str != ' ')
	    obuf->prev_ctype = mode;
    }
    (*str) += get_mclen(*str);
    obuf->flag |= RB_NFLUSHED;
}

void
push_render_image(Str str, int width, int limit,
		  struct html_feed_environ *h_env)
{
    struct readbuffer *obuf = h_env->obuf;
    int indent = h_env->envs[h_env->envc].indent;

    push_spaces(obuf, 1, (limit - width) / 2);
    push_str(obuf, width, str, PC_ASCII);
    push_spaces(obuf, 1, (limit - width + 1) / 2);
    if (width > 0)
	flushline(h_env, obuf, indent, 0, h_env->limit);
}

static int
sloppy_parse_line(char **str)
{
    if (**str == '<') {
	while (**str && **str != '>')
	    (*str)++;
	if (**str == '>')
	    (*str)++;
	return 1;
    }
    else {
	while (**str && **str != '<')
	    (*str)++;
	return 0;
    }
}

static void
passthrough(struct readbuffer *obuf, char *str, int back)
{
    int cmd;
    Str tok = Strnew();
    char *str_bak;

    if (back) {
	Str str_save = Strnew_charp(str);
	Strshrink(obuf->line, obuf->line->ptr + obuf->line->length - str);
	str = str_save->ptr;
    }
    while (*str) {
	str_bak = str;
	if (sloppy_parse_line(&str)) {
	    char *q = str_bak;
	    cmd = gethtmlcmd(&q);
	    if (back) {
		struct link_stack *p;
		for (p = link_stack; p; p = p->next) {
		    if (p->cmd == cmd) {
			link_stack = p->next;
			break;
		    }
		}
		back = 0;
	    }
	    else {
		Strcat_charp_n(tok, str_bak, str - str_bak);
		push_tag(obuf, tok->ptr, cmd);
		Strclear(tok);
	    }
	}
	else {
	    push_nchars(obuf, 0, str_bak, str - str_bak, obuf->prev_ctype);
	}
    }
}

#if 0
int
is_blank_line(char *line, int indent)
{
    int i, is_blank = 0;

    for (i = 0; i < indent; i++) {
	if (line[i] == '\0') {
	    is_blank = 1;
	}
	else if (line[i] != ' ') {
	    break;
	}
    }
    if (i == indent && line[i] == '\0')
	is_blank = 1;
    return is_blank;
}
#endif

void
fillline(struct readbuffer *obuf, int indent)
{
    push_spaces(obuf, 1, indent - obuf->pos);
    obuf->flag &= ~RB_NFLUSHED;
}

void
flushline(struct html_feed_environ *h_env, struct readbuffer *obuf, int indent,
	  int force, int width)
{
    TextLineList *buf = h_env->buf;
    FILE *f = h_env->f;
    Str line = obuf->line, pass = NULL;
    char *hidden_anchor = NULL, *hidden_img = NULL, *hidden_bold = NULL,
	*hidden_under = NULL, *hidden_italic = NULL, *hidden_strike = NULL,
	*hidden_ins = NULL, *hidden = NULL;

#ifdef DEBUG
    if (w3m_debug) {
	FILE *df = fopen("zzzproc1", "a");
	fprintf(df, "flushline(%s,%d,%d,%d)\n", obuf->line->ptr, indent, force,
		width);
	if (buf) {
	    TextLineListItem *p;
	    for (p = buf->first; p; p = p->next) {
		fprintf(df, "buf=\"%s\"\n", p->ptr->line->ptr);
	    }
	}
	fclose(df);
    }
#endif

    if (!(obuf->flag & (RB_SPECIAL & ~RB_NOBR)) && Strlastchar(line) == ' ') {
	Strshrink(line, 1);
	obuf->pos--;
    }

    append_tags(obuf);

    if (obuf->anchor.url)
	hidden = hidden_anchor = has_hidden_link(obuf, HTML_A);
    if (obuf->img_alt) {
	if ((hidden_img = has_hidden_link(obuf, HTML_IMG_ALT)) != NULL) {
	    if (!hidden || hidden_img < hidden)
		hidden = hidden_img;
	}
    }
    if (obuf->in_bold) {
	if ((hidden_bold = has_hidden_link(obuf, HTML_B)) != NULL) {
	    if (!hidden || hidden_bold < hidden)
		hidden = hidden_bold;
	}
    }
    if (obuf->in_italic) {
	if ((hidden_italic = has_hidden_link(obuf, HTML_I)) != NULL) {
	    if (!hidden || hidden_italic < hidden)
		hidden = hidden_italic;
	}
    }
    if (obuf->in_under) {
	if ((hidden_under = has_hidden_link(obuf, HTML_U)) != NULL) {
	    if (!hidden || hidden_under < hidden)
		hidden = hidden_under;
	}
    }
    if (obuf->in_strike) {
	if ((hidden_strike = has_hidden_link(obuf, HTML_S)) != NULL) {
	    if (!hidden || hidden_strike < hidden)
		hidden = hidden_strike;
	}
    }
    if (obuf->in_ins) {
	if ((hidden_ins = has_hidden_link(obuf, HTML_INS)) != NULL) {
	    if (!hidden || hidden_ins < hidden)
		hidden = hidden_ins;
	}
    }
    if (hidden) {
	pass = Strnew_charp(hidden);
	Strshrink(line, line->ptr + line->length - hidden);
    }

    if (!(obuf->flag & (RB_SPECIAL & ~RB_NOBR)) && obuf->pos > width) {
	char *tp = &line->ptr[obuf->bp.len - obuf->bp.tlen];
	char *ep = &line->ptr[line->length];

	if (obuf->bp.pos == obuf->pos && tp <= ep &&
	    tp > line->ptr && tp[-1] == ' ') {
	    bcopy(tp, tp - 1, ep - tp + 1);
	    line->length--;
	    obuf->pos--;
	}
    }

    if (obuf->anchor.url && !hidden_anchor)
	Strcat_charp(line, "</a>");
    if (obuf->img_alt && !hidden_img)
	Strcat_charp(line, "</img_alt>");
    if (obuf->in_bold && !hidden_bold)
	Strcat_charp(line, "</b>");
    if (obuf->in_italic && !hidden_italic)
	Strcat_charp(line, "</i>");
    if (obuf->in_under && !hidden_under)
	Strcat_charp(line, "</u>");
    if (obuf->in_strike && !hidden_strike)
	Strcat_charp(line, "</s>");
    if (obuf->in_ins && !hidden_ins)
	Strcat_charp(line, "</ins>");

    if (obuf->top_margin > 0) {
	int i;
	struct html_feed_environ h;
	struct readbuffer o;
	struct environment e[1];

	init_henv(&h, &o, e, 1, NULL, width, indent);
	o.line = Strnew_size(width + 20);
	o.pos = obuf->pos;
	o.flag = obuf->flag;
	o.top_margin = -1;
	o.bottom_margin = -1;
	Strcat_charp(o.line, "<pre_int>");
	for (i = 0; i < o.pos; i++)
	    Strcat_char(o.line, ' ');
	Strcat_charp(o.line, "</pre_int>");
	for (i = 0; i < obuf->top_margin; i++)
	    flushline(h_env, &o, indent, force, width);
    }

    if (force == 1 || obuf->flag & RB_NFLUSHED) {
	TextLine *lbuf = newTextLine(line, obuf->pos);
	if (RB_GET_ALIGN(obuf) == RB_CENTER) {
	    align(lbuf, width, ALIGN_CENTER);
	}
	else if (RB_GET_ALIGN(obuf) == RB_RIGHT) {
	    align(lbuf, width, ALIGN_RIGHT);
	}
	else if (RB_GET_ALIGN(obuf) == RB_LEFT && obuf->flag & RB_INTABLE) {
	    align(lbuf, width, ALIGN_LEFT);
	}
#ifdef FORMAT_NICE
	else if (obuf->flag & RB_FILL) {
	    char *p;
	    int rest, rrest;
	    int nspace, d, i;

	    rest = width - get_Str_strwidth(line);
	    if (rest > 1) {
		nspace = 0;
		for (p = line->ptr + indent; *p; p++) {
		    if (*p == ' ')
			nspace++;
		}
		if (nspace > 0) {
		    int indent_here = 0;
		    d = rest / nspace;
		    p = line->ptr;
		    while (IS_SPACE(*p)) {
			p++;
			indent_here++;
		    }
		    rrest = rest - d * nspace;
		    line = Strnew_size(width + 1);
		    for (i = 0; i < indent_here; i++)
			Strcat_char(line, ' ');
		    for (; *p; p++) {
			Strcat_char(line, *p);
			if (*p == ' ') {
			    for (i = 0; i < d; i++)
				Strcat_char(line, ' ');
			    if (rrest > 0) {
				Strcat_char(line, ' ');
				rrest--;
			    }
			}
		    }
		    lbuf = newTextLine(line, width);
		}
	    }
	}
#endif				/* FORMAT_NICE */
#ifdef TABLE_DEBUG
	if (w3m_debug) {
	    FILE *f = fopen("zzzproc1", "a");
	    fprintf(f, "pos=%d,%d, maxlimit=%d\n",
		    visible_length(lbuf->line->ptr), lbuf->pos,
		    h_env->maxlimit);
	    fclose(f);
	}
#endif
	if (lbuf->pos > h_env->maxlimit)
	    h_env->maxlimit = lbuf->pos;
	if (buf)
	    pushTextLine(buf, lbuf);
	else if (f) {
	    Strfputs(Str_conv_to_halfdump(lbuf->line), f);
	    fputc('\n', f);
	}
	if (obuf->flag & RB_SPECIAL || obuf->flag & RB_NFLUSHED)
	    h_env->blank_lines = 0;
	else
	    h_env->blank_lines++;
    }
    else {
	char *p = line->ptr, *q;
	Str tmp = Strnew(), tmp2 = Strnew();

#define APPEND(str) \
	if (buf) \
	    appendTextLine(buf,(str),0); \
	else if (f) \
	    Strfputs((str),f)

	while (*p) {
	    q = p;
	    if (sloppy_parse_line(&p)) {
		Strcat_charp_n(tmp, q, p - q);
		if (force == 2) {
		    APPEND(tmp);
		}
		else
		    Strcat(tmp2, tmp);
		Strclear(tmp);
	    }
	}
	if (force == 2) {
	    if (pass) {
		APPEND(pass);
	    }
	    pass = NULL;
	}
	else {
	    if (pass)
		Strcat(tmp2, pass);
	    pass = tmp2;
	}
    }

    if (obuf->bottom_margin > 0) {
	int i;
	struct html_feed_environ h;
	struct readbuffer o;
	struct environment e[1];

	init_henv(&h, &o, e, 1, NULL, width, indent);
	o.line = Strnew_size(width + 20);
	o.pos = obuf->pos;
	o.flag = obuf->flag;
	o.top_margin = -1;
	o.bottom_margin = -1;
	Strcat_charp(o.line, "<pre_int>");
	for (i = 0; i < o.pos; i++)
	    Strcat_char(o.line, ' ');
	Strcat_charp(o.line, "</pre_int>");
	for (i = 0; i < obuf->bottom_margin; i++)
	    flushline(h_env, &o, indent, force, width);
    }
    if (obuf->top_margin < 0 || obuf->bottom_margin < 0)
	return;

    obuf->line = Strnew_size(256);
    obuf->pos = 0;
    obuf->top_margin = 0;
    obuf->bottom_margin = 0;
    set_space_to_prevchar(obuf->prevchar);
    obuf->bp.init_flag = 1;
    obuf->flag &= ~RB_NFLUSHED;
    set_breakpoint(obuf, 0);
    obuf->prev_ctype = PC_ASCII;
    link_stack = NULL;
    fillline(obuf, indent);
    if (pass)
	passthrough(obuf, pass->ptr, 0);
    if (!hidden_anchor && obuf->anchor.url) {
	Str tmp;
	if (obuf->anchor.hseq > 0)
	    obuf->anchor.hseq = -obuf->anchor.hseq;
	tmp = Sprintf("<A HSEQ=\"%d\" HREF=\"", obuf->anchor.hseq);
	Strcat_charp(tmp, html_quote(obuf->anchor.url));
	if (obuf->anchor.target) {
	    Strcat_charp(tmp, "\" TARGET=\"");
	    Strcat_charp(tmp, html_quote(obuf->anchor.target));
	}
	if (obuf->anchor.referer) {
	    Strcat_charp(tmp, "\" REFERER=\"");
	    Strcat_charp(tmp, html_quote(obuf->anchor.referer));
	}
	if (obuf->anchor.title) {
	    Strcat_charp(tmp, "\" TITLE=\"");
	    Strcat_charp(tmp, html_quote(obuf->anchor.title));
	}
	if (obuf->anchor.accesskey) {
	    char *c = html_quote_char(obuf->anchor.accesskey);
	    Strcat_charp(tmp, "\" ACCESSKEY=\"");
	    if (c)
		Strcat_charp(tmp, c);
	    else
		Strcat_char(tmp, obuf->anchor.accesskey);
	}
	Strcat_charp(tmp, "\">");
	push_tag(obuf, tmp->ptr, HTML_A);
    }
    if (!hidden_img && obuf->img_alt) {
	Str tmp = Strnew_charp("<IMG_ALT SRC=\"");
	Strcat_charp(tmp, html_quote(obuf->img_alt->ptr));
	Strcat_charp(tmp, "\">");
	push_tag(obuf, tmp->ptr, HTML_IMG_ALT);
    }
    if (!hidden_bold && obuf->in_bold)
	push_tag(obuf, "<B>", HTML_B);
    if (!hidden_italic && obuf->in_italic)
	push_tag(obuf, "<I>", HTML_I);
    if (!hidden_under && obuf->in_under)
	push_tag(obuf, "<U>", HTML_U);
    if (!hidden_strike && obuf->in_strike)
	push_tag(obuf, "<S>", HTML_S);
    if (!hidden_ins && obuf->in_ins)
	push_tag(obuf, "<INS>", HTML_INS);
}

void
do_blankline(struct html_feed_environ *h_env, struct readbuffer *obuf,
	     int indent, int indent_incr, int width)
{
    if (h_env->blank_lines == 0)
	flushline(h_env, obuf, indent, 1, width);
}

void
purgeline(struct html_feed_environ *h_env)
{
    char *p, *q;
    Str tmp;

    if (h_env->buf == NULL || h_env->blank_lines == 0)
	return;

    p = rpopTextLine(h_env->buf)->line->ptr;
    tmp = Strnew();
    while (*p) {
	q = p;
	if (sloppy_parse_line(&p)) {
	    Strcat_charp_n(tmp, q, p - q);
	}
    }
    appendTextLine(h_env->buf, tmp, 0);
    h_env->blank_lines--;
}

static int
close_effect0(struct readbuffer *obuf, int cmd)
{
    int i;
    char *p;

    for (i = obuf->tag_sp - 1; i >= 0; i--) {
	if (obuf->tag_stack[i]->cmd == cmd)
	    break;
    }
    if (i >= 0) {
	obuf->tag_sp--;
	bcopy(&obuf->tag_stack[i + 1], &obuf->tag_stack[i],
	      (obuf->tag_sp - i) * sizeof(struct cmdtable *));
	return 1;
    }
    else if ((p = has_hidden_link(obuf, cmd)) != NULL) {
	passthrough(obuf, p, 1);
	return 1;
    }
    return 0;
}

static void
close_anchor(struct html_feed_environ *h_env, struct readbuffer *obuf)
{
    if (obuf->anchor.url) {
	int i;
	char *p = NULL;
	int is_erased = 0;

	for (i = obuf->tag_sp - 1; i >= 0; i--) {
	    if (obuf->tag_stack[i]->cmd == HTML_A)
		break;
	}
	if (i < 0 && obuf->anchor.hseq > 0 && Strlastchar(obuf->line) == ' ') {
	    Strshrink(obuf->line, 1);
	    obuf->pos--;
	    is_erased = 1;
	}

	if (i >= 0 || (p = has_hidden_link(obuf, HTML_A))) {
	    if (obuf->anchor.hseq > 0) {
		HTMLlineproc1(ANSP, h_env);
		set_space_to_prevchar(obuf->prevchar);
	    }
	    else {
		if (i >= 0) {
		    obuf->tag_sp--;
		    bcopy(&obuf->tag_stack[i + 1], &obuf->tag_stack[i],
			  (obuf->tag_sp - i) * sizeof(struct cmdtable *));
		}
		else {
		    passthrough(obuf, p, 1);
		}
		bzero((void *)&obuf->anchor, sizeof(obuf->anchor));
		return;
	    }
	    is_erased = 0;
	}
	if (is_erased) {
	    Strcat_char(obuf->line, ' ');
	    obuf->pos++;
	}

	push_tag(obuf, "</a>", HTML_N_A);
    }
    bzero((void *)&obuf->anchor, sizeof(obuf->anchor));
}

void
save_fonteffect(struct html_feed_environ *h_env, struct readbuffer *obuf)
{
    if (obuf->fontstat_sp < FONT_STACK_SIZE)
	bcopy(obuf->fontstat, obuf->fontstat_stack[obuf->fontstat_sp],
	      FONTSTAT_SIZE);
    obuf->fontstat_sp++;
    if (obuf->in_bold)
	push_tag(obuf, "</b>", HTML_N_B);
    if (obuf->in_italic)
	push_tag(obuf, "</i>", HTML_N_I);
    if (obuf->in_under)
	push_tag(obuf, "</u>", HTML_N_U);
    if (obuf->in_strike)
	push_tag(obuf, "</s>", HTML_N_S);
    if (obuf->in_ins)
	push_tag(obuf, "</ins>", HTML_N_INS);
    bzero(obuf->fontstat, FONTSTAT_SIZE);
}

void
restore_fonteffect(struct html_feed_environ *h_env, struct readbuffer *obuf)
{
    if (obuf->fontstat_sp > 0)
	obuf->fontstat_sp--;
    if (obuf->fontstat_sp < FONT_STACK_SIZE)
	bcopy(obuf->fontstat_stack[obuf->fontstat_sp], obuf->fontstat,
	      FONTSTAT_SIZE);
    if (obuf->in_bold)
	push_tag(obuf, "<b>", HTML_B);
    if (obuf->in_italic)
	push_tag(obuf, "<i>", HTML_I);
    if (obuf->in_under)
	push_tag(obuf, "<u>", HTML_U);
    if (obuf->in_strike)
	push_tag(obuf, "<s>", HTML_S);
    if (obuf->in_ins)
	push_tag(obuf, "<ins>", HTML_INS);
}

static Str
process_title(struct parsed_tag *tag)
{
    cur_title = Strnew();
    return NULL;
}

static Str
process_n_title(struct parsed_tag *tag)
{
    Str tmp;

    if (!cur_title)
	return NULL;
    Strremovefirstspaces(cur_title);
    Strremovetrailingspaces(cur_title);
    tmp = Strnew_m_charp("<title_alt title=\"",
			 html_quote(cur_title->ptr), "\">", NULL);
    cur_title = NULL;
    return tmp;
}

static void
feed_title(char *str)
{
    if (!cur_title)
	return;
    while (*str) {
	if (*str == '&')
	    Strcat_charp(cur_title, getescapecmd(&str));
	else if (*str == '\n' || *str == '\r') {
	    Strcat_char(cur_title, ' ');
	    str++;
	}
	else
	    Strcat_char(cur_title, *(str++));
    }
}

Str
process_img(struct parsed_tag *tag, int width)
{
    char *p, *q, *r, *r2 = NULL, *s, *t;
#ifdef USE_IMAGE
    int w, i, nw, ni = 1, n, w0 = -1, i0 = -1;
    int align, xoffset, yoffset, top, bottom, ismap = 0;
    int use_image = activeImage && displayImage;
#else
    int w, i, nw, n;
#endif
    int pre_int = FALSE, ext_pre_int = FALSE;
    Str tmp = Strnew();

    if (!parsedtag_get_value(tag, ATTR_SRC, &p))
	return tmp;
    p = remove_space(p);
    q = NULL;
    parsedtag_get_value(tag, ATTR_ALT, &q);
    if (!pseudoInlines && (q == NULL || (*q == '\0' && ignore_null_img_alt)))
	return tmp;
    t = q;
    parsedtag_get_value(tag, ATTR_TITLE, &t);
    w = -1;
    if (parsedtag_get_value(tag, ATTR_WIDTH, &w)) {
	if (w < 0) {
	    if (width > 0)
		w = (int)(-width * pixel_per_char * w / 100 + 0.5);
	    else
		w = -1;
	}
#ifdef USE_IMAGE
	if (use_image) {
	    if (w > 0) {
		w = (int)(w * image_scale / 100 + 0.5);
		if (w == 0)
		    w = 1;
		else if (w > MAX_IMAGE_SIZE)
		    w = MAX_IMAGE_SIZE;
	    }
	}
#endif
    }
#ifdef USE_IMAGE
    if (use_image) {
	i = -1;
	if (parsedtag_get_value(tag, ATTR_HEIGHT, &i)) {
	    if (i > 0) {
		i = (int)(i * image_scale / 100 + 0.5);
		if (i == 0)
		    i = 1;
		else if (i > MAX_IMAGE_SIZE)
		    i = MAX_IMAGE_SIZE;
	    }
	    else {
		i = -1;
	    }
	}
	align = -1;
	parsedtag_get_value(tag, ATTR_ALIGN, &align);
	ismap = 0;
	if (parsedtag_exists(tag, ATTR_ISMAP))
	    ismap = 1;
    }
    else
#endif
	parsedtag_get_value(tag, ATTR_HEIGHT, &i);
    r = NULL;
    parsedtag_get_value(tag, ATTR_USEMAP, &r);
    if (parsedtag_exists(tag, ATTR_PRE_INT))
	ext_pre_int = TRUE;

    tmp = Strnew_size(128);
#ifdef USE_IMAGE
    if (use_image) {
	switch (align) {
	case ALIGN_LEFT:
	    Strcat_charp(tmp, "<div_int align=left>");
	    break;
	case ALIGN_CENTER:
	    Strcat_charp(tmp, "<div_int align=center>");
	    break;
	case ALIGN_RIGHT:
	    Strcat_charp(tmp, "<div_int align=right>");
	    break;
	}
    }
#endif
    if (r) {
	Str tmp2;
	r2 = strchr(r, '#');
	s = "<form_int method=internal action=map>";
	tmp2 = process_form(parse_tag(&s, TRUE));
	if (tmp2)
	    Strcat(tmp, tmp2);
	Strcat(tmp, Sprintf("<input_alt fid=\"%d\" "
			    "type=hidden name=link value=\"", cur_form_id));
	Strcat_charp(tmp, html_quote((r2) ? r2 + 1 : r));
	Strcat(tmp, Sprintf("\"><input_alt hseq=\"%d\" fid=\"%d\" "
			    "type=submit no_effect=true>",
			    cur_hseq++, cur_form_id));
    }
#ifdef USE_IMAGE
    if (use_image) {
	w0 = w;
	i0 = i;
	if (w < 0 || i < 0) {
	    Image image;
	    ParsedURL u;

#ifdef USE_M17N
	    parseURL2(wc_conv(p, InnerCharset, cur_document_charset)->ptr, &u,
		      cur_baseURL);
#else
	    parseURL2(p, &u, cur_baseURL);
#endif
	    image.url = parsedURL2Str(&u)->ptr;
	    if (!uncompressed_file_type(u.file, &image.ext))
		image.ext = filename_extension(u.file, TRUE);
	    image.cache = NULL;
	    image.width = w;
	    image.height = i;

	    image.cache = getImage(&image, cur_baseURL, IMG_FLAG_SKIP);
	    if (image.cache && image.cache->width > 0 &&
		image.cache->height > 0) {
		w = w0 = image.cache->width;
		i = i0 = image.cache->height;
	    }
	    if (w < 0)
		w = 8 * pixel_per_char;
	    if (i < 0)
		i = pixel_per_line;
	}
	nw = (w > 3) ? (int)((w - 3) / pixel_per_char + 1) : 1;
	ni = (i > 3) ? (int)((i - 3) / pixel_per_line + 1) : 1;
	Strcat(tmp,
	       Sprintf("<pre_int><img_alt hseq=\"%d\" src=\"", cur_iseq++));
	pre_int = TRUE;
    }
    else
#endif
    {
	if (w < 0)
	    w = 12 * pixel_per_char;
	nw = w ? (int)((w - 1) / pixel_per_char + 1) : 1;
	if (r) {
	    Strcat_charp(tmp, "<pre_int>");
	    pre_int = TRUE;
	}
	Strcat_charp(tmp, "<img_alt src=\"");
    }
    Strcat_charp(tmp, html_quote(p));
    Strcat_charp(tmp, "\"");
    if (t) {
	Strcat_charp(tmp, " title=\"");
	Strcat_charp(tmp, html_quote(t));
	Strcat_charp(tmp, "\"");
    }
#ifdef USE_IMAGE
    if (use_image) {
	if (w0 >= 0)
	    Strcat(tmp, Sprintf(" width=%d", w0));
	if (i0 >= 0)
	    Strcat(tmp, Sprintf(" height=%d", i0));
	switch (align) {
	case ALIGN_TOP:
	    top = 0;
	    bottom = ni - 1;
	    yoffset = 0;
	    break;
	case ALIGN_MIDDLE:
	    top = ni / 2;
	    bottom = top;
	    if (top * 2 == ni)
		yoffset = (int)(((ni + 1) * pixel_per_line - i) / 2);
	    else
		yoffset = (int)((ni * pixel_per_line - i) / 2);
	    break;
	case ALIGN_BOTTOM:
	    top = ni - 1;
	    bottom = 0;
	    yoffset = (int)(ni * pixel_per_line - i);
	    break;
	default:
	    top = ni - 1;
	    bottom = 0;
	    if (ni == 1 && ni * pixel_per_line > i)
		yoffset = 0;
	    else {
		yoffset = (int)(ni * pixel_per_line - i);
		if (yoffset <= -2)
		    yoffset++;
	    }
	    break;
	}
	xoffset = (int)((nw * pixel_per_char - w) / 2);
	if (xoffset)
	    Strcat(tmp, Sprintf(" xoffset=%d", xoffset));
	if (yoffset)
	    Strcat(tmp, Sprintf(" yoffset=%d", yoffset));
	if (top)
	    Strcat(tmp, Sprintf(" top_margin=%d", top));
	if (bottom)
	    Strcat(tmp, Sprintf(" bottom_margin=%d", bottom));
	if (r) {
	    Strcat_charp(tmp, " usemap=\"");
	    Strcat_charp(tmp, html_quote((r2) ? r2 + 1 : r));
	    Strcat_charp(tmp, "\"");
	}
	if (ismap)
	    Strcat_charp(tmp, " ismap");
    }
#endif
    Strcat_charp(tmp, ">");
    if (q != NULL && *q == '\0' && ignore_null_img_alt)
	q = NULL;
    if (q != NULL) {
	n = get_strwidth(q);
#ifdef USE_IMAGE
	if (use_image) {
	    if (n > nw) {
		char *r;
		for (r = q, n = 0; r; r += get_mclen(r), n += get_mcwidth(r)) {
		    if (n + get_mcwidth(r) > nw)
			break;
		}
		Strcat_charp(tmp, html_quote(Strnew_charp_n(q, r - q)->ptr));
	    }
	    else
		Strcat_charp(tmp, html_quote(q));
	}
	else
#endif
	    Strcat_charp(tmp, html_quote(q));
	goto img_end;
    }
    if (w > 0 && i > 0) {
	/* guess what the image is! */
	if (w < 32 && i < 48) {
	    /* must be an icon or space */
	    n = 1;
	    if (strcasestr(p, "space") || strcasestr(p, "blank"))
		Strcat_charp(tmp, "_");
	    else {
		if (w * i < 8 * 16)
		    Strcat_charp(tmp, "*");
		else {
		    if (!pre_int) {
			Strcat_charp(tmp, "<pre_int>");
			pre_int = TRUE;
		    }
		    push_symbol(tmp, IMG_SYMBOL, symbol_width, 1);
		    n = symbol_width;
		}
	    }
	    goto img_end;
	}
	if (w > 200 && i < 13) {
	    /* must be a horizontal line */
	    if (!pre_int) {
		Strcat_charp(tmp, "<pre_int>");
		pre_int = TRUE;
	    }
	    w = w / pixel_per_char / symbol_width;
	    if (w <= 0)
		w = 1;
	    push_symbol(tmp, HR_SYMBOL, symbol_width, w);
	    n = w * symbol_width;
	    goto img_end;
	}
    }
    for (q = p; *q; q++) ;
    while (q > p && *q != '/')
	q--;
    if (*q == '/')
	q++;
    Strcat_char(tmp, '[');
    n = 1;
    p = q;
    for (; *q; q++) {
	if (!IS_ALNUM(*q) && *q != '_' && *q != '-') {
	    break;
	}
	Strcat_char(tmp, *q);
	n++;
	if (n + 1 >= nw)
	    break;
    }
    Strcat_char(tmp, ']');
    n++;
  img_end:
#ifdef USE_IMAGE
    if (use_image) {
	for (; n < nw; n++)
	    Strcat_char(tmp, ' ');
    }
#endif
    Strcat_charp(tmp, "</img_alt>");
    if (pre_int && !ext_pre_int)
	Strcat_charp(tmp, "</pre_int>");
    if (r) {
	Strcat_charp(tmp, "</input_alt>");
	process_n_form();
    }
#ifdef USE_IMAGE
    if (use_image) {
	switch (align) {
	case ALIGN_RIGHT:
	case ALIGN_CENTER:
	case ALIGN_LEFT:
	    Strcat_charp(tmp, "</div_int>");
	    break;
	}
    }
#endif
    return tmp;
}

Str
process_anchor(struct parsed_tag *tag, char *tagbuf)
{
    if (parsedtag_need_reconstruct(tag)) {
	parsedtag_set_value(tag, ATTR_HSEQ, Sprintf("%d", cur_hseq++)->ptr);
	return parsedtag2str(tag);
    }
    else {
	Str tmp = Sprintf("<a hseq=\"%d\"", cur_hseq++);
	Strcat_charp(tmp, tagbuf + 2);
	return tmp;
    }
}

Str
process_input(struct parsed_tag *tag)
{
    int i, w, v, x, y, z, iw, ih;
    char *q, *p, *r, *p2, *s;
    Str tmp = NULL;
    char *qq = "";
    int qlen = 0;

    if (cur_form_id < 0) {
	char *s = "<form_int method=internal action=none>";
	tmp = process_form(parse_tag(&s, TRUE));
    }
    if (tmp == NULL)
	tmp = Strnew();

    p = "text";
    parsedtag_get_value(tag, ATTR_TYPE, &p);
    q = NULL;
    parsedtag_get_value(tag, ATTR_VALUE, &q);
    r = "";
    parsedtag_get_value(tag, ATTR_NAME, &r);
    w = 20;
    parsedtag_get_value(tag, ATTR_SIZE, &w);
    i = 20;
    parsedtag_get_value(tag, ATTR_MAXLENGTH, &i);
    p2 = NULL;
    parsedtag_get_value(tag, ATTR_ALT, &p2);
    x = parsedtag_exists(tag, ATTR_CHECKED);
    y = parsedtag_exists(tag, ATTR_ACCEPT);
    z = parsedtag_exists(tag, ATTR_READONLY);

    v = formtype(p);
    if (v == FORM_UNKNOWN)
	return NULL;

    if (!q) {
	switch (v) {
	case FORM_INPUT_IMAGE:
	case FORM_INPUT_SUBMIT:
	case FORM_INPUT_BUTTON:
	    q = "SUBMIT";
	    break;
	case FORM_INPUT_RESET:
	    q = "RESET";
	    break;
	    /* if no VALUE attribute is specified in 
	     * <INPUT TYPE=CHECKBOX> tag, then the value "on" is used 
	     * as a default value. It is not a part of HTML4.0 
	     * specification, but an imitation of Netscape behaviour. 
	     */
	case FORM_INPUT_CHECKBOX:
	    q = "on";
	}
    }
    /* VALUE attribute is not allowed in <INPUT TYPE=FILE> tag. */
    if (v == FORM_INPUT_FILE)
	q = NULL;
    if (q) {
	qq = html_quote(q);
	qlen = get_strwidth(q);
    }

    Strcat_charp(tmp, "<pre_int>");
    switch (v) {
    case FORM_INPUT_PASSWORD:
    case FORM_INPUT_TEXT:
    case FORM_INPUT_FILE:
    case FORM_INPUT_CHECKBOX:
	if (displayLinkNumber)
	    Strcat(tmp, getLinkNumberStr(0));
	Strcat_char(tmp, '[');
	break;
    case FORM_INPUT_RADIO:
	if (displayLinkNumber)
	    Strcat(tmp, getLinkNumberStr(0));
	Strcat_char(tmp, '(');
    }
    Strcat(tmp, Sprintf("<input_alt hseq=\"%d\" fid=\"%d\" type=%s "
			"name=\"%s\" width=%d maxlength=%d value=\"%s\"",
			cur_hseq++, cur_form_id, p, html_quote(r), w, i, qq));
    if (x)
	Strcat_charp(tmp, " checked");
    if (y)
	Strcat_charp(tmp, " accept");
    if (z)
	Strcat_charp(tmp, " readonly");
    Strcat_char(tmp, '>');

    if (v == FORM_INPUT_HIDDEN)
	Strcat_charp(tmp, "</input_alt></pre_int>");
    else {
	switch (v) {
	case FORM_INPUT_PASSWORD:
	case FORM_INPUT_TEXT:
	case FORM_INPUT_FILE:
	    Strcat_charp(tmp, "<u>");
	    break;
	case FORM_INPUT_IMAGE:
	    s = NULL;
	    parsedtag_get_value(tag, ATTR_SRC, &s);
	    if (s) {
		Strcat(tmp, Sprintf("<img src=\"%s\"", html_quote(s)));
		if (p2)
		    Strcat(tmp, Sprintf(" alt=\"%s\"", html_quote(p2)));
		if (parsedtag_get_value(tag, ATTR_WIDTH, &iw))
		    Strcat(tmp, Sprintf(" width=\"%d\"", iw));
		if (parsedtag_get_value(tag, ATTR_HEIGHT, &ih))
		    Strcat(tmp, Sprintf(" height=\"%d\"", ih));
		Strcat_charp(tmp, " pre_int>");
		Strcat_charp(tmp, "</input_alt></pre_int>");
		return tmp;
	    }
	case FORM_INPUT_SUBMIT:
	case FORM_INPUT_BUTTON:
	case FORM_INPUT_RESET:
	    if (displayLinkNumber)
		Strcat(tmp, getLinkNumberStr(-1));
	    Strcat_charp(tmp, "[");
	    break;
	}
	switch (v) {
	case FORM_INPUT_PASSWORD:
	    i = 0;
	    if (q) {
		for (; i < qlen && i < w; i++)
		    Strcat_char(tmp, '*');
	    }
	    for (; i < w; i++)
		Strcat_char(tmp, ' ');
	    break;
	case FORM_INPUT_TEXT:
	case FORM_INPUT_FILE:
	    if (q)
		Strcat(tmp, textfieldrep(Strnew_charp(q), w));
	    else {
		for (i = 0; i < w; i++)
		    Strcat_char(tmp, ' ');
	    }
	    break;
	case FORM_INPUT_SUBMIT:
	case FORM_INPUT_BUTTON:
	    if (p2)
		Strcat_charp(tmp, html_quote(p2));
	    else
		Strcat_charp(tmp, qq);
	    break;
	case FORM_INPUT_RESET:
	    Strcat_charp(tmp, qq);
	    break;
	case FORM_INPUT_RADIO:
	case FORM_INPUT_CHECKBOX:
	    if (x)
		Strcat_char(tmp, '*');
	    else
		Strcat_char(tmp, ' ');
	    break;
	}
	switch (v) {
	case FORM_INPUT_PASSWORD:
	case FORM_INPUT_TEXT:
	case FORM_INPUT_FILE:
	    Strcat_charp(tmp, "</u>");
	    break;
	case FORM_INPUT_IMAGE:
	case FORM_INPUT_SUBMIT:
	case FORM_INPUT_BUTTON:
	case FORM_INPUT_RESET:
	    Strcat_charp(tmp, "]");
	}
	Strcat_charp(tmp, "</input_alt>");
	switch (v) {
	case FORM_INPUT_PASSWORD:
	case FORM_INPUT_TEXT:
	case FORM_INPUT_FILE:
	case FORM_INPUT_CHECKBOX:
	    Strcat_char(tmp, ']');
	    break;
	case FORM_INPUT_RADIO:
	    Strcat_char(tmp, ')');
	}
	Strcat_charp(tmp, "</pre_int>");
    }
    return tmp;
}

Str
process_select(struct parsed_tag *tag)
{
    Str tmp = NULL;
    char *p;

    if (cur_form_id < 0) {
	char *s = "<form_int method=internal action=none>";
	tmp = process_form(parse_tag(&s, TRUE));
    }

    p = "";
    parsedtag_get_value(tag, ATTR_NAME, &p);
    cur_select = Strnew_charp(p);
    select_is_multiple = parsedtag_exists(tag, ATTR_MULTIPLE);

#ifdef MENU_SELECT
    if (!select_is_multiple) {
	select_str = Strnew_charp("<pre_int>");
	if (displayLinkNumber)
	    Strcat(select_str, getLinkNumberStr(0));
	Strcat(select_str, Sprintf("[<input_alt hseq=\"%d\" "
			     "fid=\"%d\" type=select name=\"%s\" selectnumber=%d",
			     cur_hseq++, cur_form_id, html_quote(p), n_select));
	Strcat_charp(select_str, ">");
	if (n_select == max_select) {
	    max_select *= 2;
	    select_option =
		New_Reuse(FormSelectOption, select_option, max_select);
	}
	select_option[n_select].first = NULL;
	select_option[n_select].last = NULL;
	cur_option_maxwidth = 0;
    }
    else
#endif				/* MENU_SELECT */
	select_str = Strnew();
    cur_option = NULL;
    cur_status = R_ST_NORMAL;
    n_selectitem = 0;
    return tmp;
}

Str
process_n_select(void)
{
    if (cur_select == NULL)
	return NULL;
    process_option();
#ifdef MENU_SELECT
    if (!select_is_multiple) {
	if (select_option[n_select].first) {
	    FormItemList sitem;
	    chooseSelectOption(&sitem, select_option[n_select].first);
	    Strcat(select_str, textfieldrep(sitem.label, cur_option_maxwidth));
	}
	Strcat_charp(select_str, "</input_alt>]</pre_int>");
	n_select++;
    }
    else
#endif				/* MENU_SELECT */
	Strcat_charp(select_str, "<br>");
    cur_select = NULL;
    n_selectitem = 0;
    return select_str;
}

void
feed_select(char *str)
{
    Str tmp = Strnew();
    int prev_status = cur_status;
    static int prev_spaces = -1;
    char *p;

    if (cur_select == NULL)
	return;
    while (read_token(tmp, &str, &cur_status, 0, 0)) {
	if (cur_status != R_ST_NORMAL || prev_status != R_ST_NORMAL)
	    continue;
	p = tmp->ptr;
	if (tmp->ptr[0] == '<' && Strlastchar(tmp) == '>') {
	    struct parsed_tag *tag;
	    char *q;
	    if (!(tag = parse_tag(&p, FALSE)))
		continue;
	    switch (tag->tagid) {
	    case HTML_OPTION:
		process_option();
		cur_option = Strnew();
		if (parsedtag_get_value(tag, ATTR_VALUE, &q))
		    cur_option_value = Strnew_charp(q);
		else
		    cur_option_value = NULL;
		if (parsedtag_get_value(tag, ATTR_LABEL, &q))
		    cur_option_label = Strnew_charp(q);
		else
		    cur_option_label = NULL;
		cur_option_selected = parsedtag_exists(tag, ATTR_SELECTED);
		prev_spaces = -1;
		break;
	    case HTML_N_OPTION:
		/* do nothing */
		break;
	    default:
		/* never happen */
		break;
	    }
	}
	else if (cur_option) {
	    while (*p) {
		if (IS_SPACE(*p) && prev_spaces != 0) {
		    p++;
		    if (prev_spaces > 0)
			prev_spaces++;
		}
		else {
		    if (IS_SPACE(*p))
			prev_spaces = 1;
		    else
			prev_spaces = 0;
		    if (*p == '&')
			Strcat_charp(cur_option, getescapecmd(&p));
		    else
			Strcat_char(cur_option, *(p++));
		}
	    }
	}
    }
}

void
process_option(void)
{
    char begin_char = '[', end_char = ']';
    int len;

    if (cur_select == NULL || cur_option == NULL)
	return;
    while (cur_option->length > 0 && IS_SPACE(Strlastchar(cur_option)))
	Strshrink(cur_option, 1);
    if (cur_option_value == NULL)
	cur_option_value = cur_option;
    if (cur_option_label == NULL)
	cur_option_label = cur_option;
#ifdef MENU_SELECT
    if (!select_is_multiple) {
	len = get_Str_strwidth(cur_option_label);
	if (len > cur_option_maxwidth)
	    cur_option_maxwidth = len;
	addSelectOption(&select_option[n_select],
			cur_option_value,
			cur_option_label, cur_option_selected);
	return;
    }
#endif				/* MENU_SELECT */
    if (!select_is_multiple) {
	begin_char = '(';
	end_char = ')';
    }
    Strcat(select_str, Sprintf("<br><pre_int>%c<input_alt hseq=\"%d\" "
			       "fid=\"%d\" type=%s name=\"%s\" value=\"%s\"",
			       begin_char, cur_hseq++, cur_form_id,
			       select_is_multiple ? "checkbox" : "radio",
			       html_quote(cur_select->ptr),
			       html_quote(cur_option_value->ptr)));
    if (cur_option_selected)
	Strcat_charp(select_str, " checked>*</input_alt>");
    else
	Strcat_charp(select_str, "> </input_alt>");
    Strcat_char(select_str, end_char);
    Strcat_charp(select_str, html_quote(cur_option_label->ptr));
    Strcat_charp(select_str, "</pre_int>");
    n_selectitem++;
}

Str
process_textarea(struct parsed_tag *tag, int width)
{
    Str tmp = NULL;
    char *p;
#define TEXTAREA_ATTR_COL_MAX 4096
#define TEXTAREA_ATTR_ROWS_MAX 4096

    if (cur_form_id < 0) {
	char *s = "<form_int method=internal action=none>";
	tmp = process_form(parse_tag(&s, TRUE));
    }

    p = "";
    parsedtag_get_value(tag, ATTR_NAME, &p);
    cur_textarea = Strnew_charp(p);
    cur_textarea_size = 20;
    if (parsedtag_get_value(tag, ATTR_COLS, &p)) {
	cur_textarea_size = atoi(p);
	if (p[strlen(p) - 1] == '%')
	    cur_textarea_size = width * cur_textarea_size / 100 - 2;
	if (cur_textarea_size <= 0) {
	    cur_textarea_size = 20;
	} else if (cur_textarea_size > TEXTAREA_ATTR_COL_MAX) {
	    cur_textarea_size = TEXTAREA_ATTR_COL_MAX;
	}
    }
    cur_textarea_rows = 1;
    if (parsedtag_get_value(tag, ATTR_ROWS, &p)) {
	cur_textarea_rows = atoi(p);
	if (cur_textarea_rows <= 0) {
	    cur_textarea_rows = 1;
	} else if (cur_textarea_rows > TEXTAREA_ATTR_ROWS_MAX) {
	    cur_textarea_rows = TEXTAREA_ATTR_ROWS_MAX;
	}
    }
    cur_textarea_readonly = parsedtag_exists(tag, ATTR_READONLY);
    if (n_textarea >= max_textarea) {
	max_textarea *= 2;
	textarea_str = New_Reuse(Str, textarea_str, max_textarea);
    }
    textarea_str[n_textarea] = Strnew();
    ignore_nl_textarea = TRUE;

    return tmp;
}

Str
process_n_textarea(void)
{
    Str tmp;
    int i;

    if (cur_textarea == NULL)
	return NULL;

    tmp = Strnew();
    Strcat(tmp, Sprintf("<pre_int>[<input_alt hseq=\"%d\" fid=\"%d\" "
			"type=textarea name=\"%s\" size=%d rows=%d "
			"top_margin=%d textareanumber=%d",
			cur_hseq, cur_form_id,
			html_quote(cur_textarea->ptr),
			cur_textarea_size, cur_textarea_rows,
			cur_textarea_rows - 1, n_textarea));
    if (cur_textarea_readonly)
	Strcat_charp(tmp, " readonly");
    Strcat_charp(tmp, "><u>");
    for (i = 0; i < cur_textarea_size; i++)
	Strcat_char(tmp, ' ');
    Strcat_charp(tmp, "</u></input_alt>]</pre_int>\n");
    cur_hseq++;
    n_textarea++;
    cur_textarea = NULL;

    return tmp;
}

void
feed_textarea(char *str)
{
    if (cur_textarea == NULL)
	return;
    if (ignore_nl_textarea) {
	if (*str == '\r')
	    str++;
	if (*str == '\n')
	    str++;
    }
    ignore_nl_textarea = FALSE;
    while (*str) {
	if (*str == '&')
	    Strcat_charp(textarea_str[n_textarea], getescapecmd(&str));
	else if (*str == '\n') {
	    Strcat_charp(textarea_str[n_textarea], "\r\n");
	    str++;
	}
	else if (*str != '\r')
	    Strcat_char(textarea_str[n_textarea], *(str++));
    }
}

Str
process_hr(struct parsed_tag *tag, int width, int indent_width)
{
    Str tmp = Strnew_charp("<nobr>");
    int w = 0;
    int x = ALIGN_CENTER;
#define HR_ATTR_WIDTH_MAX 65535

    if (width > indent_width)
	width -= indent_width;
    if (parsedtag_get_value(tag, ATTR_WIDTH, &w)) {
	if (w > HR_ATTR_WIDTH_MAX) {
	    w = HR_ATTR_WIDTH_MAX;
	}
	w = REAL_WIDTH(w, width);
    } else {
	w = width;
    }

    parsedtag_get_value(tag, ATTR_ALIGN, &x);
    switch (x) {
    case ALIGN_CENTER:
	Strcat_charp(tmp, "<div_int align=center>");
	break;
    case ALIGN_RIGHT:
	Strcat_charp(tmp, "<div_int align=right>");
	break;
    case ALIGN_LEFT:
	Strcat_charp(tmp, "<div_int align=left>");
	break;
    }
    w /= symbol_width;
    if (w <= 0)
	w = 1;
    push_symbol(tmp, HR_SYMBOL, symbol_width, w);
    Strcat_charp(tmp, "</div_int></nobr>");
    return tmp;
}

#ifdef USE_M17N
static char *
check_charset(char *p)
{
    return wc_guess_charset(p, 0) ? p : NULL;
}

static char *
check_accept_charset(char *ac)
{
    char *s = ac, *e;

    while (*s) {
	while (*s && (IS_SPACE(*s) || *s == ','))
	    s++;
	if (!*s)
	    break;
	e = s;
	while (*e && !(IS_SPACE(*e) || *e == ','))
	    e++;
	if (wc_guess_charset(Strnew_charp_n(s, e - s)->ptr, 0))
	    return ac;
	s = e;
    }
    return NULL;
}
#endif

static Str
process_form_int(struct parsed_tag *tag, int fid)
{
    char *p, *q, *r, *s, *tg, *n;

    p = "get";
    parsedtag_get_value(tag, ATTR_METHOD, &p);
    q = "!CURRENT_URL!";
    parsedtag_get_value(tag, ATTR_ACTION, &q);
    r = NULL;
#ifdef USE_M17N
    if (parsedtag_get_value(tag, ATTR_ACCEPT_CHARSET, &r))
	r = check_accept_charset(r);
    if (!r && parsedtag_get_value(tag, ATTR_CHARSET, &r))
	r = check_charset(r);
#endif
    s = NULL;
    parsedtag_get_value(tag, ATTR_ENCTYPE, &s);
    tg = NULL;
    parsedtag_get_value(tag, ATTR_TARGET, &tg);
    n = NULL;
    parsedtag_get_value(tag, ATTR_NAME, &n);

    if (fid < 0) {
	form_max++;
	form_sp++;
	fid = form_max;
    }
    else {			/* <form_int> */
	if (form_max < fid)
	    form_max = fid;
	form_sp = fid;
    }
    if (forms_size == 0) {
	forms_size = INITIAL_FORM_SIZE;
	forms = New_N(FormList *, forms_size);
	form_stack = NewAtom_N(int, forms_size);
    }
    else if (forms_size <= form_max) {
	forms_size += form_max;
	forms = New_Reuse(FormList *, forms, forms_size);
	form_stack = New_Reuse(int, form_stack, forms_size);
    }
    form_stack[form_sp] = fid;

    if (w3m_halfdump) {
	Str tmp = Sprintf("<form_int fid=\"%d\" action=\"%s\" method=\"%s\"",
			  fid, html_quote(q), html_quote(p));
	if (s)
	    Strcat(tmp, Sprintf(" enctype=\"%s\"", html_quote(s)));
	if (tg)
	    Strcat(tmp, Sprintf(" target=\"%s\"", html_quote(tg)));
	if (n)
	    Strcat(tmp, Sprintf(" name=\"%s\"", html_quote(n)));
#ifdef USE_M17N
	if (r)
	    Strcat(tmp, Sprintf(" accept-charset=\"%s\"", html_quote(r)));
#endif
	Strcat_charp(tmp, ">");
	return tmp;
    }

    forms[fid] = newFormList(q, p, r, s, tg, n, NULL);
    return NULL;
}

Str
process_form(struct parsed_tag *tag)
{
    return process_form_int(tag, -1);
}

Str
process_n_form(void)
{
    if (form_sp >= 0)
	form_sp--;
    return NULL;
}

static void
clear_ignore_p_flag(int cmd, struct readbuffer *obuf)
{
    static int clear_flag_cmd[] = {
	HTML_HR, HTML_UNKNOWN
    };
    int i;

    for (i = 0; clear_flag_cmd[i] != HTML_UNKNOWN; i++) {
	if (cmd == clear_flag_cmd[i]) {
	    obuf->flag &= ~RB_IGNORE_P;
	    return;
	}
    }
}

static void
set_alignment(struct readbuffer *obuf, struct parsed_tag *tag)
{
    long flag = -1;
    int align;

    if (parsedtag_get_value(tag, ATTR_ALIGN, &align)) {
	switch (align) {
	case ALIGN_CENTER:
	    flag = RB_CENTER;
	    break;
	case ALIGN_RIGHT:
	    flag = RB_RIGHT;
	    break;
	case ALIGN_LEFT:
	    flag = RB_LEFT;
	}
    }
    RB_SAVE_FLAG(obuf);
    if (flag != -1) {
	RB_SET_ALIGN(obuf, flag);
    }
}

#ifdef ID_EXT
static void
process_idattr(struct readbuffer *obuf, int cmd, struct parsed_tag *tag)
{
    char *id = NULL, *framename = NULL;
    Str idtag = NULL;

    /* 
     * HTML_TABLE is handled by the other process.
     */
    if (cmd == HTML_TABLE)
	return;

    parsedtag_get_value(tag, ATTR_ID, &id);
    parsedtag_get_value(tag, ATTR_FRAMENAME, &framename);
    if (id == NULL)
	return;
    if (framename)
	idtag = Sprintf("<_id id=\"%s\" framename=\"%s\">",
			html_quote(id), html_quote(framename));
    else
	idtag = Sprintf("<_id id=\"%s\">", html_quote(id));
    push_tag(obuf, idtag->ptr, HTML_NOP);
}
#endif				/* ID_EXT */

#define CLOSE_P if (obuf->flag & RB_P) { \
      flushline(h_env, obuf, envs[h_env->envc].indent,0,h_env->limit);\
      RB_RESTORE_FLAG(obuf);\
      obuf->flag &= ~RB_P;\
    }

#define CLOSE_A \
    CLOSE_P; \
    close_anchor(h_env, obuf);

#define CLOSE_DT \
    if (obuf->flag & RB_IN_DT) { \
      obuf->flag &= ~RB_IN_DT; \
      HTMLlineproc1("</b>", h_env); \
    }

#define PUSH_ENV(cmd) \
    if (++h_env->envc_real < h_env->nenv) { \
      ++h_env->envc; \
      envs[h_env->envc].env = cmd; \
      envs[h_env->envc].count = 0; \
      if (h_env->envc <= MAX_INDENT_LEVEL) \
        envs[h_env->envc].indent = envs[h_env->envc - 1].indent + INDENT_INCR; \
      else \
        envs[h_env->envc].indent = envs[h_env->envc - 1].indent; \
    }

#define POP_ENV \
    if (h_env->envc_real-- < h_env->nenv) \
      h_env->envc--;

static int
ul_type(struct parsed_tag *tag, int default_type)
{
    char *p;
    if (parsedtag_get_value(tag, ATTR_TYPE, &p)) {
	if (!strcasecmp(p, "disc"))
	    return (int)'d';
	else if (!strcasecmp(p, "circle"))
	    return (int)'c';
	else if (!strcasecmp(p, "square"))
	    return (int)'s';
    }
    return default_type;
}

int
getMetaRefreshParam(char *q, Str *refresh_uri)
{
    int refresh_interval;
    char *r;
    Str s_tmp = NULL;

    if (q == NULL || refresh_uri == NULL)
	return 0;

    refresh_interval = atoi(q);
    if (refresh_interval < 0)
	return 0;

    while (*q) {
	if (!strncasecmp(q, "url=", 4)) {
	    q += 4;
	    if (*q == '\"')	/* " */
		q++;
	    r = q;
	    while (*r && !IS_SPACE(*r) && *r != ';')
		r++;
	    s_tmp = Strnew_charp_n(q, r - q);

	    if (s_tmp->ptr[s_tmp->length - 1] == '\"') {	/* " 
								 */
		s_tmp->length--;
		s_tmp->ptr[s_tmp->length] = '\0';
	    }
	    q = r;
	}
	while (*q && *q != ';')
	    q++;
	if (*q == ';')
	    q++;
	while (*q && *q == ' ')
	    q++;
    }
    *refresh_uri = s_tmp;
    return refresh_interval;
}

int
HTMLtagproc1(struct parsed_tag *tag, struct html_feed_environ *h_env)
{
    char *p, *q, *r;
    int i, w, x, y, z, count, width;
    struct readbuffer *obuf = h_env->obuf;
    struct environment *envs = h_env->envs;
    Str tmp;
    int hseq;
    int cmd;
#ifdef ID_EXT
    char *id = NULL;
#endif				/* ID_EXT */

    cmd = tag->tagid;

    if (obuf->flag & RB_PRE) {
	switch (cmd) {
	case HTML_NOBR:
	case HTML_N_NOBR:
	case HTML_PRE_INT:
	case HTML_N_PRE_INT:
	    return 1;
	}
    }

    switch (cmd) {
    case HTML_B:
	obuf->in_bold++;
	if (obuf->in_bold > 1)
	    return 1;
	return 0;
    case HTML_N_B:
	if (obuf->in_bold == 1 && close_effect0(obuf, HTML_B))
	    obuf->in_bold = 0;
	if (obuf->in_bold > 0) {
	    obuf->in_bold--;
	    if (obuf->in_bold == 0)
		return 0;
	}
	return 1;
    case HTML_I:
	obuf->in_italic++;
	if (obuf->in_italic > 1)
	    return 1;
	return 0;
    case HTML_N_I:
	if (obuf->in_italic == 1 && close_effect0(obuf, HTML_I))
	    obuf->in_italic = 0;
	if (obuf->in_italic > 0) {
	    obuf->in_italic--;
	    if (obuf->in_italic == 0)
		return 0;
	}
	return 1;
    case HTML_U:
	obuf->in_under++;
	if (obuf->in_under > 1)
	    return 1;
	return 0;
    case HTML_N_U:
	if (obuf->in_under == 1 && close_effect0(obuf, HTML_U))
	    obuf->in_under = 0;
	if (obuf->in_under > 0) {
	    obuf->in_under--;
	    if (obuf->in_under == 0)
		return 0;
	}
	return 1;
    case HTML_EM:
	HTMLlineproc1("<i>", h_env);
	return 1;
    case HTML_N_EM:
	HTMLlineproc1("</i>", h_env);
	return 1;
    case HTML_STRONG:
	HTMLlineproc1("<b>", h_env);
	return 1;
    case HTML_N_STRONG:
	HTMLlineproc1("</b>", h_env);
	return 1;
    case HTML_Q:
	HTMLlineproc1("`", h_env);
	return 1;
    case HTML_N_Q:
	HTMLlineproc1("'", h_env);
	return 1;
    case HTML_P:
    case HTML_N_P:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 1, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	obuf->flag |= RB_IGNORE_P;
	if (cmd == HTML_P) {
	    set_alignment(obuf, tag);
	    obuf->flag |= RB_P;
	}
	return 1;
    case HTML_BR:
	flushline(h_env, obuf, envs[h_env->envc].indent, 1, h_env->limit);
	h_env->blank_lines = 0;
	return 1;
    case HTML_H:
	if (!(obuf->flag & (RB_PREMODE | RB_IGNORE_P))) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	HTMLlineproc1("<b>", h_env);
	set_alignment(obuf, tag);
	return 1;
    case HTML_N_H:
	HTMLlineproc1("</b>", h_env);
	if (!(obuf->flag & RB_PREMODE)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	}
	do_blankline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	close_anchor(h_env, obuf);
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_UL:
    case HTML_OL:
    case HTML_BLQ:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    if (!(obuf->flag & RB_PREMODE) &&
		(h_env->envc == 0 || cmd == HTML_BLQ))
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
	}
	PUSH_ENV(cmd);
	if (cmd == HTML_UL || cmd == HTML_OL) {
	    if (parsedtag_get_value(tag, ATTR_START, &count)) {
		envs[h_env->envc].count = count - 1;
	    }
	}
	if (cmd == HTML_OL) {
	    envs[h_env->envc].type = '1';
	    if (parsedtag_get_value(tag, ATTR_TYPE, &p)) {
		envs[h_env->envc].type = (int)*p;
	    }
	}
	if (cmd == HTML_UL)
	    envs[h_env->envc].type = ul_type(tag, 0);
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	return 1;
    case HTML_N_UL:
    case HTML_N_OL:
    case HTML_N_DL:
    case HTML_N_BLQ:
	CLOSE_DT;
	CLOSE_A;
	if (h_env->envc > 0) {
	    flushline(h_env, obuf, envs[h_env->envc - 1].indent, 0,
		      h_env->limit);
	    POP_ENV;
	    if (!(obuf->flag & RB_PREMODE) &&
		(h_env->envc == 0 || cmd == HTML_N_DL || cmd == HTML_N_BLQ)) {
		do_blankline(h_env, obuf,
			     envs[h_env->envc].indent,
			     INDENT_INCR, h_env->limit);
		obuf->flag |= RB_IGNORE_P;
	    }
	}
	close_anchor(h_env, obuf);
	return 1;
    case HTML_DL:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    if (!(obuf->flag & RB_PREMODE))
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
	}
	PUSH_ENV(cmd);
	if (parsedtag_exists(tag, ATTR_COMPACT))
	    envs[h_env->envc].env = HTML_DL_COMPACT;
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_LI:
	CLOSE_A;
	CLOSE_DT;
	if (h_env->envc > 0) {
	    Str num;
	    flushline(h_env, obuf,
		      envs[h_env->envc - 1].indent, 0, h_env->limit);
	    envs[h_env->envc].count++;
	    if (parsedtag_get_value(tag, ATTR_VALUE, &p)) {
		count = atoi(p);
		if (count > 0)
		    envs[h_env->envc].count = count;
		else
		    envs[h_env->envc].count = 0;
	    }
	    switch (envs[h_env->envc].env) {
	    case HTML_UL:
		envs[h_env->envc].type = ul_type(tag, envs[h_env->envc].type);
		for (i = 0; i < INDENT_INCR - 3; i++)
		    push_charp(obuf, 1, NBSP, PC_ASCII);
		tmp = Strnew();
		switch (envs[h_env->envc].type) {
		case 'd':
		    push_symbol(tmp, UL_SYMBOL_DISC, symbol_width, 1);
		    break;
		case 'c':
		    push_symbol(tmp, UL_SYMBOL_CIRCLE, symbol_width, 1);
		    break;
		case 's':
		    push_symbol(tmp, UL_SYMBOL_SQUARE, symbol_width, 1);
		    break;
		default:
		    push_symbol(tmp,
				UL_SYMBOL((h_env->envc_real -
					   1) % MAX_UL_LEVEL), symbol_width,
				1);
		    break;
		}
		if (symbol_width == 1)
		    push_charp(obuf, 1, NBSP, PC_ASCII);
		push_str(obuf, symbol_width, tmp, PC_ASCII);
		push_charp(obuf, 1, NBSP, PC_ASCII);
		set_space_to_prevchar(obuf->prevchar);
		break;
	    case HTML_OL:
		if (parsedtag_get_value(tag, ATTR_TYPE, &p))
		    envs[h_env->envc].type = (int)*p;
		switch ((envs[h_env->envc].count > 0)? envs[h_env->envc].type: '1') {
		case 'i':
		    num = romanNumeral(envs[h_env->envc].count);
		    break;
		case 'I':
		    num = romanNumeral(envs[h_env->envc].count);
		    Strupper(num);
		    break;
		case 'a':
		    num = romanAlphabet(envs[h_env->envc].count);
		    break;
		case 'A':
		    num = romanAlphabet(envs[h_env->envc].count);
		    Strupper(num);
		    break;
		default:
		    num = Sprintf("%d", envs[h_env->envc].count);
		    break;
		}
		if (INDENT_INCR >= 4)
		    Strcat_charp(num, ". ");
		else
		    Strcat_char(num, '.');
		push_spaces(obuf, 1, INDENT_INCR - num->length);
		push_str(obuf, num->length, num, PC_ASCII);
		if (INDENT_INCR >= 4)
		    set_space_to_prevchar(obuf->prevchar);
		break;
	    default:
		push_spaces(obuf, 1, INDENT_INCR);
		break;
	    }
	}
	else {
	    flushline(h_env, obuf, 0, 0, h_env->limit);
	}
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_DT:
	CLOSE_A;
	if (h_env->envc == 0 ||
	    (h_env->envc_real < h_env->nenv &&
	     envs[h_env->envc].env != HTML_DL &&
	     envs[h_env->envc].env != HTML_DL_COMPACT)) {
	    PUSH_ENV(HTML_DL);
	}
	if (h_env->envc > 0) {
	    flushline(h_env, obuf,
		      envs[h_env->envc - 1].indent, 0, h_env->limit);
	}
	if (!(obuf->flag & RB_IN_DT)) {
	    HTMLlineproc1("<b>", h_env);
	    obuf->flag |= RB_IN_DT;
	}
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_DD:
	CLOSE_A;
	CLOSE_DT;
	if (envs[h_env->envc].env == HTML_DL_COMPACT) {
	    if (obuf->pos > envs[h_env->envc].indent)
		flushline(h_env, obuf, envs[h_env->envc].indent, 0,
			  h_env->limit);
	    else
		push_spaces(obuf, 1, envs[h_env->envc].indent - obuf->pos);
	}
	else
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	/* obuf->flag |= RB_IGNORE_P; */
	return 1;
    case HTML_TITLE:
	close_anchor(h_env, obuf);
	process_title(tag);
	obuf->flag |= RB_TITLE;
	obuf->end_tag = HTML_N_TITLE;
	return 1;
    case HTML_N_TITLE:
	if (!(obuf->flag & RB_TITLE))
	    return 1;
	obuf->flag &= ~RB_TITLE;
	obuf->end_tag = 0;
	tmp = process_n_title(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_TITLE_ALT:
	if (parsedtag_get_value(tag, ATTR_TITLE, &p))
	    h_env->title = html_unquote(p);
	return 0;
    case HTML_FRAMESET:
	PUSH_ENV(cmd);
	push_charp(obuf, 9, "--FRAME--", PC_ASCII);
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	return 0;
    case HTML_N_FRAMESET:
	if (h_env->envc > 0) {
	    POP_ENV;
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	}
	return 0;
    case HTML_NOFRAMES:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	obuf->flag |= (RB_NOFRAMES | RB_IGNORE_P);
	/* istr = str; */
	return 1;
    case HTML_N_NOFRAMES:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	obuf->flag &= ~RB_NOFRAMES;
	return 1;
    case HTML_FRAME:
	q = r = NULL;
	parsedtag_get_value(tag, ATTR_SRC, &q);
	parsedtag_get_value(tag, ATTR_NAME, &r);
	if (q) {
	    q = html_quote(q);
	    push_tag(obuf, Sprintf("<a hseq=\"%d\" href=\"%s\">",
				   cur_hseq++, q)->ptr, HTML_A);
	    if (r)
		q = html_quote(r);
	    push_charp(obuf, get_strwidth(q), q, PC_ASCII);
	    push_tag(obuf, "</a>", HTML_N_A);
	}
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	return 0;
    case HTML_HR:
	close_anchor(h_env, obuf);
	tmp = process_hr(tag, h_env->limit, envs[h_env->envc].indent);
	HTMLlineproc1(tmp->ptr, h_env);
	set_space_to_prevchar(obuf->prevchar);
	return 1;
    case HTML_PRE:
	x = parsedtag_exists(tag, ATTR_FOR_TABLE);
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    if (!x)
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
	}
	else
	    fillline(obuf, envs[h_env->envc].indent);
	obuf->flag |= (RB_PRE | RB_IGNORE_P);
	/* istr = str; */
	return 1;
    case HTML_N_PRE:
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	if (!(obuf->flag & RB_IGNORE_P)) {
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	    obuf->flag |= RB_IGNORE_P;
	    h_env->blank_lines++;
	}
	obuf->flag &= ~RB_PRE;
	close_anchor(h_env, obuf);
	return 1;
    case HTML_PRE_INT:
	i = obuf->line->length;
	append_tags(obuf);
	if (!(obuf->flag & RB_SPECIAL)) {
	    set_breakpoint(obuf, obuf->line->length - i);
	}
	obuf->flag |= RB_PRE_INT;
	return 0;
    case HTML_N_PRE_INT:
	push_tag(obuf, "</pre_int>", HTML_N_PRE_INT);
	obuf->flag &= ~RB_PRE_INT;
	if (!(obuf->flag & RB_SPECIAL) && obuf->pos > obuf->bp.pos) {
	    set_prevchar(obuf->prevchar, "", 0);
	    obuf->prev_ctype = PC_CTRL;
	}
	return 1;
    case HTML_NOBR:
	obuf->flag |= RB_NOBR;
	obuf->nobr_level++;
	return 0;
    case HTML_N_NOBR:
	if (obuf->nobr_level > 0)
	    obuf->nobr_level--;
	if (obuf->nobr_level == 0)
	    obuf->flag &= ~RB_NOBR;
	return 0;
    case HTML_PRE_PLAIN:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	obuf->flag |= (RB_PRE | RB_IGNORE_P);
	return 1;
    case HTML_N_PRE_PLAIN:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	    obuf->flag |= RB_IGNORE_P;
	}
	obuf->flag &= ~RB_PRE;
	return 1;
    case HTML_LISTING:
    case HTML_XMP:
    case HTML_PLAINTEXT:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	obuf->flag |= (RB_PLAIN | RB_IGNORE_P);
	switch (cmd) {
	case HTML_LISTING:
	    obuf->end_tag = HTML_N_LISTING;
	    break;
	case HTML_XMP:
	    obuf->end_tag = HTML_N_XMP;
	    break;
	case HTML_PLAINTEXT:
	    obuf->end_tag = MAX_HTMLTAG;
	    break;
	}
	return 1;
    case HTML_N_LISTING:
    case HTML_N_XMP:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	    obuf->flag |= RB_IGNORE_P;
	}
	obuf->flag &= ~RB_PLAIN;
	obuf->end_tag = 0;
	return 1;
    case HTML_SCRIPT:
	obuf->flag |= RB_SCRIPT;
	obuf->end_tag = HTML_N_SCRIPT;
	return 1;
    case HTML_STYLE:
	obuf->flag |= RB_STYLE;
	obuf->end_tag = HTML_N_STYLE;
	return 1;
    case HTML_N_SCRIPT:
	obuf->flag &= ~RB_SCRIPT;
	obuf->end_tag = 0;
	return 1;
    case HTML_N_STYLE:
	obuf->flag &= ~RB_STYLE;
	obuf->end_tag = 0;
	return 1;
    case HTML_A:
	if (obuf->anchor.url)
	    close_anchor(h_env, obuf);

	hseq = 0;

	if (parsedtag_get_value(tag, ATTR_HREF, &p))
	    obuf->anchor.url = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_TARGET, &p))
	    obuf->anchor.target = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_REFERER, &p))
	    obuf->anchor.referer = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_TITLE, &p))
	    obuf->anchor.title = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_ACCESSKEY, &p))
	    obuf->anchor.accesskey = (unsigned char)*p;
	if (parsedtag_get_value(tag, ATTR_HSEQ, &hseq))
	    obuf->anchor.hseq = hseq;

	if (hseq == 0 && obuf->anchor.url) {
	    obuf->anchor.hseq = cur_hseq;
	    tmp = process_anchor(tag, h_env->tagbuf->ptr);
	    push_tag(obuf, tmp->ptr, HTML_A);
	    if (displayLinkNumber)
		HTMLlineproc1(getLinkNumberStr(-1)->ptr, h_env);
	    return 1;
	}
	return 0;
    case HTML_N_A:
	close_anchor(h_env, obuf);
	return 1;
    case HTML_IMG:
	tmp = process_img(tag, h_env->limit);
	HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_IMG_ALT:
	if (parsedtag_get_value(tag, ATTR_SRC, &p))
	    obuf->img_alt = Strnew_charp(p);
#ifdef USE_IMAGE
	i = 0;
	if (parsedtag_get_value(tag, ATTR_TOP_MARGIN, &i)) {
	    if (i > obuf->top_margin)
		obuf->top_margin = i;
	}
	i = 0;
	if (parsedtag_get_value(tag, ATTR_BOTTOM_MARGIN, &i)) {
	    if (i > obuf->bottom_margin)
		obuf->bottom_margin = i;
	}
#endif
	return 0;
    case HTML_N_IMG_ALT:
	if (obuf->img_alt) {
	    if (!close_effect0(obuf, HTML_IMG_ALT))
		push_tag(obuf, "</img_alt>", HTML_N_IMG_ALT);
	    obuf->img_alt = NULL;
	}
	return 1;
    case HTML_INPUT_ALT:
	i = 0;
	if (parsedtag_get_value(tag, ATTR_TOP_MARGIN, &i)) {
	    if (i > obuf->top_margin)
		obuf->top_margin = i;
	}
	i = 0;
	if (parsedtag_get_value(tag, ATTR_BOTTOM_MARGIN, &i)) {
	    if (i > obuf->bottom_margin)
		obuf->bottom_margin = i;
	}
	return 0;
    case HTML_TABLE:
	close_anchor(h_env, obuf);
	obuf->table_level++;
	if (obuf->table_level >= MAX_TABLE)
	    break;
	w = BORDER_NONE;
	/* x: cellspacing, y: cellpadding */
	x = 2;
	y = 1;
	z = 0;
	width = 0;
	if (parsedtag_exists(tag, ATTR_BORDER)) {
	    if (parsedtag_get_value(tag, ATTR_BORDER, &w)) {
		if (w > 2)
		    w = BORDER_THICK;
		else if (w < 0) {	/* weird */
		    w = BORDER_THIN;
		}
	    }
	    else
		w = BORDER_THIN;
	}
	if (parsedtag_get_value(tag, ATTR_WIDTH, &i)) {
	    if (obuf->table_level == 0)
		width = REAL_WIDTH(i, h_env->limit - envs[h_env->envc].indent);
	    else
		width = RELATIVE_WIDTH(i);
	}
	if (parsedtag_exists(tag, ATTR_HBORDER))
	    w = BORDER_NOWIN;
	parsedtag_get_value(tag, ATTR_CELLSPACING, &x);
	parsedtag_get_value(tag, ATTR_CELLPADDING, &y);
	parsedtag_get_value(tag, ATTR_VSPACE, &z);
#ifdef ID_EXT
	parsedtag_get_value(tag, ATTR_ID, &id);
#endif				/* ID_EXT */
	tables[obuf->table_level] = begin_table(w, x, y, z);
#ifdef ID_EXT
	if (id != NULL)
	    tables[obuf->table_level]->id = Strnew_charp(id);
#endif				/* ID_EXT */
	table_mode[obuf->table_level].pre_mode = 0;
	table_mode[obuf->table_level].indent_level = 0;
	table_mode[obuf->table_level].nobr_level = 0;
	table_mode[obuf->table_level].caption = 0;
	table_mode[obuf->table_level].end_tag = 0;	/* HTML_UNKNOWN */
#ifndef TABLE_EXPAND
	tables[obuf->table_level]->total_width = width;
#else
	tables[obuf->table_level]->real_width = width;
	tables[obuf->table_level]->total_width = 0;
#endif
	return 1;
    case HTML_N_TABLE:
	/* should be processed in HTMLlineproc() */
	return 1;
    case HTML_CENTER:
	CLOSE_A;
	if (!(obuf->flag & (RB_PREMODE | RB_IGNORE_P)))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_SAVE_FLAG(obuf);
	RB_SET_ALIGN(obuf, RB_CENTER);
	return 1;
    case HTML_N_CENTER:
	CLOSE_A;
	if (!(obuf->flag & RB_PREMODE))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	return 1;
    case HTML_DIV:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	set_alignment(obuf, tag);
	return 1;
    case HTML_N_DIV:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	return 1;
    case HTML_DIV_INT:
	CLOSE_P;
	if (!(obuf->flag & RB_IGNORE_P))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	set_alignment(obuf, tag);
	return 1;
    case HTML_N_DIV_INT:
	CLOSE_P;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	return 1;
    case HTML_FORM:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	tmp = process_form(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_N_FORM:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	obuf->flag |= RB_IGNORE_P;
	process_n_form();
	return 1;
    case HTML_INPUT:
	close_anchor(h_env, obuf);
	tmp = process_input(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_SELECT:
	close_anchor(h_env, obuf);
	tmp = process_select(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	obuf->flag |= RB_INSELECT;
	obuf->end_tag = HTML_N_SELECT;
	return 1;
    case HTML_N_SELECT:
	obuf->flag &= ~RB_INSELECT;
	obuf->end_tag = 0;
	tmp = process_n_select();
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_OPTION:
	/* nothing */
	return 1;
    case HTML_TEXTAREA:
	close_anchor(h_env, obuf);
	tmp = process_textarea(tag, h_env->limit);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	obuf->flag |= RB_INTXTA;
	obuf->end_tag = HTML_N_TEXTAREA;
	return 1;
    case HTML_N_TEXTAREA:
	obuf->flag &= ~RB_INTXTA;
	obuf->end_tag = 0;
	tmp = process_n_textarea();
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_ISINDEX:
	p = "";
	q = "!CURRENT_URL!";
	parsedtag_get_value(tag, ATTR_PROMPT, &p);
	parsedtag_get_value(tag, ATTR_ACTION, &q);
	tmp = Strnew_m_charp("<form method=get action=\"",
			     html_quote(q),
			     "\">",
			     html_quote(p),
			     "<input type=text name=\"\" accept></form>",
			     NULL);
	HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_META:
	p = q = NULL;
	parsedtag_get_value(tag, ATTR_HTTP_EQUIV, &p);
	parsedtag_get_value(tag, ATTR_CONTENT, &q);
#ifdef USE_M17N
	if (p && q && !strcasecmp(p, "Content-Type") &&
	    (q = strcasestr(q, "charset")) != NULL) {
	    q += 7;
	    SKIP_BLANKS(q);
	    if (*q == '=') {
		q++;
		SKIP_BLANKS(q);
		meta_charset = wc_guess_charset(q, 0);
	    }
	}
	else
#endif
	if (p && q && !strcasecmp(p, "refresh")) {
	    int refresh_interval;
	    tmp = NULL;
	    refresh_interval = getMetaRefreshParam(q, &tmp);
	    if (tmp) {
		q = html_quote(tmp->ptr);
		tmp = Sprintf("Refresh (%d sec) <a href=\"%s\">%s</a>",
			      refresh_interval, q, q);
	    }
	    else if (refresh_interval > 0)
		tmp = Sprintf("Refresh (%d sec)", refresh_interval);
	    if (tmp) {
		HTMLlineproc1(tmp->ptr, h_env);
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
		if (!is_redisplay &&
		    !((obuf->flag & RB_NOFRAMES) && RenderFrame)) {
		    tag->need_reconstruct = TRUE;
		    return 0;
		}
	    }
	}
	return 1;
    case HTML_BASE:
#ifdef USE_IMAGE
	p = NULL;
	if (parsedtag_get_value(tag, ATTR_HREF, &p)) {
	    if (!cur_baseURL)
		cur_baseURL = New(ParsedURL);
	    parseURL(p, cur_baseURL, NULL);
	}
#endif
    case HTML_MAP:
    case HTML_N_MAP:
    case HTML_AREA:
	return 0;
    case HTML_DEL:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag |= RB_DEL;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("<U>[DEL:</U>", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    obuf->in_strike++;
	    if (obuf->in_strike == 1) {
		push_tag(obuf, "<s>", HTML_S);
	    }
	    break;
	}
	return 1;
    case HTML_N_DEL:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag &= ~RB_DEL;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("<U>:DEL]</U>", h_env);
	case DISPLAY_INS_DEL_FONTIFY:
	    if (obuf->in_strike == 0)
		return 1;
	    if (obuf->in_strike == 1 && close_effect0(obuf, HTML_S))
		obuf->in_strike = 0;
	    if (obuf->in_strike > 0) {
		obuf->in_strike--;
		if (obuf->in_strike == 0) {
		    push_tag(obuf, "</s>", HTML_N_S);
		}
	    }
	    break;
	}
	return 1;
    case HTML_S:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag |= RB_S;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("<U>[S:</U>", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    obuf->in_strike++;
	    if (obuf->in_strike == 1) {
		push_tag(obuf, "<s>", HTML_S);
	    }
	    break;
	}
	return 1;
    case HTML_N_S:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag &= ~RB_S;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("<U>:S]</U>", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    if (obuf->in_strike == 0)
		return 1;
	    if (obuf->in_strike == 1 && close_effect0(obuf, HTML_S))
		obuf->in_strike = 0;
	    if (obuf->in_strike > 0) {
		obuf->in_strike--;
		if (obuf->in_strike == 0) {
		    push_tag(obuf, "</s>", HTML_N_S);
		}
	    }
	}
	return 1;
    case HTML_INS:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("<U>[INS:</U>", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    obuf->in_ins++;
	    if (obuf->in_ins == 1) {
		push_tag(obuf, "<ins>", HTML_INS);
	    }
	    break;
	}
	return 1;
    case HTML_N_INS:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("<U>:INS]</U>", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    if (obuf->in_ins == 0)
		return 1;
	    if (obuf->in_ins == 1 && close_effect0(obuf, HTML_INS))
		obuf->in_ins = 0;
	    if (obuf->in_ins > 0) {
		obuf->in_ins--;
		if (obuf->in_ins == 0) {
		    push_tag(obuf, "</ins>", HTML_N_INS);
		}
	    }
	    break;
	}
	return 1;
    case HTML_SUP:
	if (!(obuf->flag & (RB_DEL | RB_S)))
	    HTMLlineproc1("^", h_env);
	return 1;
    case HTML_N_SUP:
	return 1;
    case HTML_SUB:
	if (!(obuf->flag & (RB_DEL | RB_S)))
	    HTMLlineproc1("[", h_env);
	return 1;
    case HTML_N_SUB:
	if (!(obuf->flag & (RB_DEL | RB_S)))
	    HTMLlineproc1("]", h_env);
	return 1;
    case HTML_FONT:
    case HTML_N_FONT:
    case HTML_NOP:
	return 1;
    case HTML_BGSOUND:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_SRC, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("<A HREF=\"%s\">bgsound(%s)</A>", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
	return 1;
    case HTML_EMBED:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_SRC, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("<A HREF=\"%s\">embed(%s)</A>", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
	return 1;
    case HTML_APPLET:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_ARCHIVE, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("<A HREF=\"%s\">applet archive(%s)</A>", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
	return 1;
    case HTML_BODY:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_BACKGROUND, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("<IMG SRC=\"%s\" ALT=\"bg image(%s)\"><BR>", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
    case HTML_N_HEAD:
	if (obuf->flag & RB_TITLE)
	    HTMLlineproc1("</title>", h_env);
    case HTML_HEAD:
    case HTML_N_BODY:
	return 1;
    default:
	/* obuf->prevchar = '\0'; */
	return 0;
    }
    /* not reached */
    return 0;
}

#define PPUSH(p,c) {outp[pos]=(p);outc[pos]=(c);pos++;}
#define PSIZE	\
    if (out_size <= pos + 1) {	\
	out_size = pos * 3 / 2;	\
	outc = New_Reuse(char, outc, out_size);	\
	outp = New_Reuse(Lineprop, outp, out_size);	\
    }

static TextLineListItem *_tl_lp2;

static Str
textlist_feed()
{
    TextLine *p;
    if (_tl_lp2 != NULL) {
	p = _tl_lp2->ptr;
	_tl_lp2 = _tl_lp2->next;
	return p->line;
    }
    return NULL;
}

static int
ex_efct(int ex)
{
    int effect = 0;

    if (! ex)
	return 0;

    if (ex & PE_EX_ITALIC)
	effect |= PE_EX_ITALIC_E;

    if (ex & PE_EX_INSERT)
	effect |= PE_EX_INSERT_E;

    if (ex & PE_EX_STRIKE)
	effect |= PE_EX_STRIKE_E;

    return effect;
}

static void
HTMLlineproc2body(Buffer *buf, Str (*feed) (), int llimit)
{
    static char *outc = NULL;
    static Lineprop *outp = NULL;
    static int out_size = 0;
    Anchor *a_href = NULL, *a_img = NULL, *a_form = NULL;
    char *p, *q, *r, *s, *t, *str;
    Lineprop mode, effect, ex_effect;
    int pos;
    int nlines;
#ifdef DEBUG
    FILE *debug = NULL;
#endif
    struct frameset *frameset_s[FRAMESTACK_SIZE];
    int frameset_sp = -1;
    union frameset_element *idFrame = NULL;
    char *id = NULL;
    int hseq, form_id;
    Str line;
    char *endp;
    char symbol = '\0';
    int internal = 0;
    Anchor **a_textarea = NULL;
#ifdef MENU_SELECT
    Anchor **a_select = NULL;
#endif

    if (out_size == 0) {
	out_size = LINELEN;
	outc = NewAtom_N(char, out_size);
	outp = NewAtom_N(Lineprop, out_size);
    }

    n_textarea = -1;
    if (!max_textarea) {	/* halfload */
	max_textarea = MAX_TEXTAREA;
	textarea_str = New_N(Str, max_textarea);
	a_textarea = New_N(Anchor *, max_textarea);
    }
#ifdef MENU_SELECT
    n_select = -1;
    if (!max_select) {		/* halfload */
	max_select = MAX_SELECT;
	select_option = New_N(FormSelectOption, max_select);
	a_select = New_N(Anchor *, max_select);
    }
#endif

#ifdef DEBUG
    if (w3m_debug)
	debug = fopen("zzzerr", "a");
#endif

    effect = 0;
    ex_effect = 0;
    nlines = 0;
    while ((line = feed()) != NULL) {
#ifdef DEBUG
	if (w3m_debug) {
	    Strfputs(line, debug);
	    fputc('\n', debug);
	}
#endif
	if (n_textarea >= 0 && *(line->ptr) != '<') {	/* halfload */
	    Strcat(textarea_str[n_textarea], line);
	    continue;
	}
      proc_again:
	if (++nlines == llimit)
	    break;
	pos = 0;
#ifdef ENABLE_REMOVE_TRAILINGSPACES
	Strremovetrailingspaces(line);
#endif
	str = line->ptr;
	endp = str + line->length;
	while (str < endp) {
	    PSIZE;
	    mode = get_mctype(str);
	    if ((effect | ex_efct(ex_effect)) & PC_SYMBOL && *str != '<') {
#ifdef USE_M17N
		char **buf = set_symbol(symbol_width0);
		int len;

		p = buf[(int)symbol];
		len = get_mclen(p);
		mode = get_mctype(p);
		PPUSH(mode | effect | ex_efct(ex_effect), *(p++));
		if (--len) {
		    mode = (mode & ~PC_WCHAR1) | PC_WCHAR2;
		    while (len--) {
			PSIZE;
			PPUSH(mode | effect | ex_efct(ex_effect), *(p++));
		    }
		}
#else
		PPUSH(PC_ASCII | effect | ex_efct(ex_effect), SYMBOL_BASE + symbol);
#endif
		str += symbol_width;
	    }
#ifdef USE_M17N
	    else if (mode == PC_CTRL || mode == PC_UNDEF) {
#else
	    else if (mode == PC_CTRL || IS_INTSPACE(*str)) {
#endif
		PPUSH(PC_ASCII | effect | ex_efct(ex_effect), ' ');
		str++;
	    }
#ifdef USE_M17N
	    else if (mode & PC_UNKNOWN) {
		PPUSH(PC_ASCII | effect | ex_efct(ex_effect), ' ');
		str += get_mclen(str);
	    }
#endif
	    else if (*str != '<' && *str != '&') {
#ifdef USE_M17N
		int len = get_mclen(str);
#endif
		PPUSH(mode | effect | ex_efct(ex_effect), *(str++));
#ifdef USE_M17N
		if (--len) {
		    mode = (mode & ~PC_WCHAR1) | PC_WCHAR2;
		    while (len--) {
			PSIZE;
			PPUSH(mode | effect | ex_efct(ex_effect), *(str++));
		    }
		}
#endif
	    }
	    else if (*str == '&') {
		/* 
		 * & escape processing
		 */
		p = getescapecmd(&str);
		while (*p) {
		    PSIZE;
		    mode = get_mctype((unsigned char *)p);
#ifdef USE_M17N
		    if (mode == PC_CTRL || mode == PC_UNDEF) {
#else
		    if (mode == PC_CTRL || IS_INTSPACE(*str)) {
#endif
			PPUSH(PC_ASCII | effect | ex_efct(ex_effect), ' ');
			p++;
		    }
#ifdef USE_M17N
		    else if (mode & PC_UNKNOWN) {
			PPUSH(PC_ASCII | effect | ex_efct(ex_effect), ' ');
			p += get_mclen(p);
		    }
#endif
		    else {
#ifdef USE_M17N
			int len = get_mclen(p);
#endif
			PPUSH(mode | effect | ex_efct(ex_effect), *(p++));
#ifdef USE_M17N
			if (--len) {
			    mode = (mode & ~PC_WCHAR1) | PC_WCHAR2;
			    while (len--) {
				PSIZE;
				PPUSH(mode | effect | ex_efct(ex_effect), *(p++));
			    }
			}
#endif
		    }
		}
	    }
	    else {
		/* tag processing */
		struct parsed_tag *tag;
		if (!(tag = parse_tag(&str, TRUE)))
		    continue;
		switch (tag->tagid) {
		case HTML_B:
		    effect |= PE_BOLD;
		    break;
		case HTML_N_B:
		    effect &= ~PE_BOLD;
		    break;
		case HTML_I:
		    ex_effect |= PE_EX_ITALIC;
		    break;
		case HTML_N_I:
		    ex_effect &= ~PE_EX_ITALIC;
		    break;
		case HTML_INS:
		    ex_effect |= PE_EX_INSERT;
		    break;
		case HTML_N_INS:
		    ex_effect &= ~PE_EX_INSERT;
		    break;
		case HTML_U:
		    effect |= PE_UNDER;
		    break;
		case HTML_N_U:
		    effect &= ~PE_UNDER;
		    break;
		case HTML_S:
		    ex_effect |= PE_EX_STRIKE;
		    break;
		case HTML_N_S:
		    ex_effect &= ~PE_EX_STRIKE;
		    break;
		case HTML_A:
		    if (renderFrameSet &&
			parsedtag_get_value(tag, ATTR_FRAMENAME, &p)) {
			p = url_quote_conv(p, buf->document_charset);
			if (!idFrame || strcmp(idFrame->body->name, p)) {
			    idFrame = search_frame(renderFrameSet, p);
			    if (idFrame && idFrame->body->attr != F_BODY)
				idFrame = NULL;
			}
		    }
		    p = r = s = NULL;
		    q = buf->baseTarget;
		    t = "";
		    hseq = 0;
		    id = NULL;
		    if (parsedtag_get_value(tag, ATTR_NAME, &id)) {
			id = url_quote_conv(id, buf->document_charset);
			registerName(buf, id, currentLn(buf), pos);
		    }
		    if (parsedtag_get_value(tag, ATTR_HREF, &p))
			p = url_quote_conv(remove_space(p),
					   buf->document_charset);
		    if (parsedtag_get_value(tag, ATTR_TARGET, &q))
			q = url_quote_conv(q, buf->document_charset);
		    if (parsedtag_get_value(tag, ATTR_REFERER, &r))
			r = url_quote_conv(r, buf->document_charset);
		    parsedtag_get_value(tag, ATTR_TITLE, &s);
		    parsedtag_get_value(tag, ATTR_ACCESSKEY, &t);
		    parsedtag_get_value(tag, ATTR_HSEQ, &hseq);
		    if (hseq > 0)
			buf->hmarklist =
			    putHmarker(buf->hmarklist, currentLn(buf),
				       pos, hseq - 1);
		    else if (hseq < 0) {
			int h = -hseq - 1;
			if (buf->hmarklist &&
			    h < buf->hmarklist->nmark &&
			    buf->hmarklist->marks[h].invalid) {
			    buf->hmarklist->marks[h].pos = pos;
			    buf->hmarklist->marks[h].line = currentLn(buf);
			    buf->hmarklist->marks[h].invalid = 0;
			    hseq = -hseq;
			}
		    }
		    if (id && idFrame)
			idFrame->body->nameList =
			    putAnchor(idFrame->body->nameList, id, NULL,
				      (Anchor **)NULL, NULL, NULL, '\0',
				      currentLn(buf), pos);
		    if (p) {
			effect |= PE_ANCHOR;
			a_href = registerHref(buf, p, q, r, s,
					      *t, currentLn(buf), pos);
			a_href->hseq = ((hseq > 0) ? hseq : -hseq) - 1;
			a_href->slave = (hseq > 0) ? FALSE : TRUE;
		    }
		    break;
		case HTML_N_A:
		    effect &= ~PE_ANCHOR;
		    if (a_href) {
			a_href->end.line = currentLn(buf);
			a_href->end.pos = pos;
			if (a_href->start.line == a_href->end.line &&
			    a_href->start.pos == a_href->end.pos) {
			    if (buf->hmarklist &&
				a_href->hseq < buf->hmarklist->nmark)
				buf->hmarklist->marks[a_href->hseq].invalid = 1;
			    a_href->hseq = -1;
			}
			a_href = NULL;
		    }
		    break;

		case HTML_LINK:
		    addLink(buf, tag);
		    break;

		case HTML_IMG_ALT:
		    if (parsedtag_get_value(tag, ATTR_SRC, &p)) {
#ifdef USE_IMAGE
			int w = -1, h = -1, iseq = 0, ismap = 0;
			int xoffset = 0, yoffset = 0, top = 0, bottom = 0;
			parsedtag_get_value(tag, ATTR_HSEQ, &iseq);
			parsedtag_get_value(tag, ATTR_WIDTH, &w);
			parsedtag_get_value(tag, ATTR_HEIGHT, &h);
			parsedtag_get_value(tag, ATTR_XOFFSET, &xoffset);
			parsedtag_get_value(tag, ATTR_YOFFSET, &yoffset);
			parsedtag_get_value(tag, ATTR_TOP_MARGIN, &top);
			parsedtag_get_value(tag, ATTR_BOTTOM_MARGIN, &bottom);
			if (parsedtag_exists(tag, ATTR_ISMAP))
			    ismap = 1;
			q = NULL;
			parsedtag_get_value(tag, ATTR_USEMAP, &q);
			if (iseq > 0) {
			    buf->imarklist = putHmarker(buf->imarklist,
							currentLn(buf), pos,
							iseq - 1);
			}
#endif
			s = NULL;
			parsedtag_get_value(tag, ATTR_TITLE, &s);
			p = url_quote_conv(remove_space(p),
					   buf->document_charset);
			a_img = registerImg(buf, p, s, currentLn(buf), pos);
#ifdef USE_IMAGE
			a_img->hseq = iseq;
			a_img->image = NULL;
			if (iseq > 0) {
			    ParsedURL u;
			    Image *image;

			    parseURL2(a_img->url, &u, cur_baseURL);
			    a_img->image = image = New(Image);
			    image->url = parsedURL2Str(&u)->ptr;
			    if (!uncompressed_file_type(u.file, &image->ext))
				image->ext = filename_extension(u.file, TRUE);
			    image->cache = NULL;
			    image->width =
				(w > MAX_IMAGE_SIZE) ? MAX_IMAGE_SIZE : w;
			    image->height =
				(h > MAX_IMAGE_SIZE) ? MAX_IMAGE_SIZE : h;
			    image->xoffset = xoffset;
			    image->yoffset = yoffset;
			    image->y = currentLn(buf) - top;
			    if (image->xoffset < 0 && pos == 0)
				image->xoffset = 0;
			    if (image->yoffset < 0 && image->y == 1)
				image->yoffset = 0;
			    image->rows = 1 + top + bottom;
			    image->map = q;
			    image->ismap = ismap;
			    image->touch = 0;
			    image->cache = getImage(image, cur_baseURL,
						    IMG_FLAG_SKIP);
			}
			else if (iseq < 0) {
			    BufferPoint *po = buf->imarklist->marks - iseq - 1;
			    Anchor *a = retrieveAnchor(buf->img,
						       po->line, po->pos);
			    if (a) {
				a_img->url = a->url;
				a_img->image = a->image;
			    }
			}
#endif
		    }
		    effect |= PE_IMAGE;
		    break;
		case HTML_N_IMG_ALT:
		    effect &= ~PE_IMAGE;
		    if (a_img) {
			a_img->end.line = currentLn(buf);
			a_img->end.pos = pos;
		    }
		    a_img = NULL;
		    break;
		case HTML_INPUT_ALT:
		    {
			FormList *form;
			int top = 0, bottom = 0;
			int textareanumber = -1;
#ifdef MENU_SELECT
			int selectnumber = -1;
#endif
			hseq = 0;
			form_id = -1;

			parsedtag_get_value(tag, ATTR_HSEQ, &hseq);
			parsedtag_get_value(tag, ATTR_FID, &form_id);
			parsedtag_get_value(tag, ATTR_TOP_MARGIN, &top);
			parsedtag_get_value(tag, ATTR_BOTTOM_MARGIN, &bottom);
			if (form_id < 0 || form_id > form_max || forms == NULL)
			    break;	/* outside of <form>..</form> */
			form = forms[form_id];
			if (hseq > 0) {
			    int hpos = pos;
			    if (*str == '[')
				hpos++;
			    buf->hmarklist =
				putHmarker(buf->hmarklist, currentLn(buf),
					   hpos, hseq - 1);
			}
			if (!form->target)
			    form->target = buf->baseTarget;
			if (a_textarea &&
			    parsedtag_get_value(tag, ATTR_TEXTAREANUMBER,
						&textareanumber)) {
			    if (textareanumber >= max_textarea) {
				max_textarea = 2 * textareanumber;
				textarea_str = New_Reuse(Str, textarea_str,
							 max_textarea);
				a_textarea = New_Reuse(Anchor *, a_textarea,
						       max_textarea);
			    }
			}
#ifdef MENU_SELECT
			if (a_select &&
			    parsedtag_get_value(tag, ATTR_SELECTNUMBER,
						&selectnumber)) {
			    if (selectnumber >= max_select) {
				max_select = 2 * selectnumber;
				select_option = New_Reuse(FormSelectOption,
							  select_option,
							  max_select);
				a_select = New_Reuse(Anchor *, a_select,
						     max_select);
			    }
			}
#endif
			a_form =
			    registerForm(buf, form, tag, currentLn(buf), pos);
			if (a_textarea && textareanumber >= 0)
			    a_textarea[textareanumber] = a_form;
#ifdef MENU_SELECT
			if (a_select && selectnumber >= 0)
			    a_select[selectnumber] = a_form;
#endif
			if (a_form) {
			    a_form->hseq = hseq - 1;
			    a_form->y = currentLn(buf) - top;
			    a_form->rows = 1 + top + bottom;
			    if (!parsedtag_exists(tag, ATTR_NO_EFFECT))
				effect |= PE_FORM;
			    break;
			}
		    }
		case HTML_N_INPUT_ALT:
		    effect &= ~PE_FORM;
		    if (a_form) {
			a_form->end.line = currentLn(buf);
			a_form->end.pos = pos;
			if (a_form->start.line == a_form->end.line &&
			    a_form->start.pos == a_form->end.pos)
			    a_form->hseq = -1;
		    }
		    a_form = NULL;
		    break;
		case HTML_MAP:
		    if (parsedtag_get_value(tag, ATTR_NAME, &p)) {
			MapList *m = New(MapList);
			m->name = Strnew_charp(p);
			m->area = newGeneralList();
			m->next = buf->maplist;
			buf->maplist = m;
		    }
		    break;
		case HTML_N_MAP:
		    /* nothing to do */
		    break;
		case HTML_AREA:
		    if (buf->maplist == NULL)	/* outside of <map>..</map> */
			break;
		    if (parsedtag_get_value(tag, ATTR_HREF, &p)) {
			MapArea *a;
			p = url_quote_conv(remove_space(p),
					   buf->document_charset);
			t = NULL;
			parsedtag_get_value(tag, ATTR_TARGET, &t);
			q = "";
			parsedtag_get_value(tag, ATTR_ALT, &q);
			r = NULL;
			s = NULL;
#ifdef USE_IMAGE
			parsedtag_get_value(tag, ATTR_SHAPE, &r);
			parsedtag_get_value(tag, ATTR_COORDS, &s);
#endif
			a = newMapArea(p, t, q, r, s);
			pushValue(buf->maplist->area, (void *)a);
		    }
		    break;
		case HTML_FRAMESET:
		    frameset_sp++;
		    if (frameset_sp >= FRAMESTACK_SIZE)
			break;
		    frameset_s[frameset_sp] = newFrameSet(tag);
		    if (frameset_s[frameset_sp] == NULL)
			break;
		    if (frameset_sp == 0) {
			if (buf->frameset == NULL) {
			    buf->frameset = frameset_s[frameset_sp];
			}
			else
			    pushFrameTree(&(buf->frameQ),
					  frameset_s[frameset_sp], NULL);
		    }
		    else
			addFrameSetElement(frameset_s[frameset_sp - 1],
					   *(union frameset_element *)
					   &frameset_s[frameset_sp]);
		    break;
		case HTML_N_FRAMESET:
		    if (frameset_sp >= 0)
			frameset_sp--;
		    break;
		case HTML_FRAME:
		    if (frameset_sp >= 0 && frameset_sp < FRAMESTACK_SIZE) {
			union frameset_element element;

			element.body = newFrame(tag, buf);
			addFrameSetElement(frameset_s[frameset_sp], element);
		    }
		    break;
		case HTML_BASE:
		    if (parsedtag_get_value(tag, ATTR_HREF, &p)) {
			p = url_quote_conv(remove_space(p),
					   buf->document_charset);
			if (!buf->baseURL)
			    buf->baseURL = New(ParsedURL);
			parseURL(p, buf->baseURL, NULL);
		    }
		    if (parsedtag_get_value(tag, ATTR_TARGET, &p))
			buf->baseTarget =
			    url_quote_conv(p, buf->document_charset);
		    break;
		case HTML_META:
		    p = q = NULL;
		    parsedtag_get_value(tag, ATTR_HTTP_EQUIV, &p);
		    parsedtag_get_value(tag, ATTR_CONTENT, &q);
		    if (p && q && !strcasecmp(p, "refresh") && MetaRefresh) {
			Str tmp = NULL;
			int refresh_interval = getMetaRefreshParam(q, &tmp);
#ifdef USE_ALARM
			if (tmp) {
			    p = url_quote_conv(remove_space(tmp->ptr),
					       buf->document_charset);
			    buf->event = setAlarmEvent(buf->event,
						       refresh_interval,
						       AL_IMPLICIT_ONCE,
						       FUNCNAME_gorURL, p);
			}
			else if (refresh_interval > 0)
			    buf->event = setAlarmEvent(buf->event,
						       refresh_interval,
						       AL_IMPLICIT,
						       FUNCNAME_reload, NULL);
#else
			if (tmp && refresh_interval == 0) {
			    p = url_quote_conv(remove_space(tmp->ptr),
					       buf->document_charset);
			    pushEvent(FUNCNAME_gorURL, p);
			}
#endif
		    }
		    break;
		case HTML_INTERNAL:
		    internal = HTML_INTERNAL;
		    break;
		case HTML_N_INTERNAL:
		    internal = HTML_N_INTERNAL;
		    break;
		case HTML_FORM_INT:
		    if (parsedtag_get_value(tag, ATTR_FID, &form_id))
			process_form_int(tag, form_id);
		    break;
		case HTML_TEXTAREA_INT:
		    if (parsedtag_get_value(tag, ATTR_TEXTAREANUMBER,
					    &n_textarea)
			&& n_textarea < max_textarea) {
			textarea_str[n_textarea] = Strnew();
		    }
		    else
			n_textarea = -1;
		    break;
		case HTML_N_TEXTAREA_INT:
		    if (n_textarea >= 0) {
			FormItemList *item =
			    (FormItemList *)a_textarea[n_textarea]->url;
			item->init_value = item->value =
			    textarea_str[n_textarea];
		    }
		    break;
#ifdef MENU_SELECT
		case HTML_SELECT_INT:
		    if (parsedtag_get_value(tag, ATTR_SELECTNUMBER, &n_select)
			&& n_select < max_select) {
			select_option[n_select].first = NULL;
			select_option[n_select].last = NULL;
		    }
		    else
			n_select = -1;
		    break;
		case HTML_N_SELECT_INT:
		    if (n_select >= 0) {
			FormItemList *item =
			    (FormItemList *)a_select[n_select]->url;
			item->select_option = select_option[n_select].first;
			chooseSelectOption(item, item->select_option);
			item->init_selected = item->selected;
			item->init_value = item->value;
			item->init_label = item->label;
		    }
		    break;
		case HTML_OPTION_INT:
		    if (n_select >= 0) {
			int selected;
			q = "";
			parsedtag_get_value(tag, ATTR_LABEL, &q);
			p = q;
			parsedtag_get_value(tag, ATTR_VALUE, &p);
			selected = parsedtag_exists(tag, ATTR_SELECTED);
			addSelectOption(&select_option[n_select],
					Strnew_charp(p), Strnew_charp(q),
					selected);
		    }
		    break;
#endif
		case HTML_TITLE_ALT:
		    if (parsedtag_get_value(tag, ATTR_TITLE, &p))
			buf->buffername = html_unquote(p);
		    break;
		case HTML_SYMBOL:
		    effect |= PC_SYMBOL;
		    if (parsedtag_get_value(tag, ATTR_TYPE, &p))
			symbol = (char)atoi(p);
		    break;
		case HTML_N_SYMBOL:
		    effect &= ~PC_SYMBOL;
		    break;
		}
#ifdef	ID_EXT
		id = NULL;
		if (parsedtag_get_value(tag, ATTR_ID, &id)) {
		    id = url_quote_conv(id, buf->document_charset);
		    registerName(buf, id, currentLn(buf), pos);
		}
		if (renderFrameSet &&
		    parsedtag_get_value(tag, ATTR_FRAMENAME, &p)) {
		    p = url_quote_conv(p, buf->document_charset);
		    if (!idFrame || strcmp(idFrame->body->name, p)) {
			idFrame = search_frame(renderFrameSet, p);
			if (idFrame && idFrame->body->attr != F_BODY)
			    idFrame = NULL;
		    }
		}
		if (id && idFrame)
		    idFrame->body->nameList =
			putAnchor(idFrame->body->nameList, id, NULL,
				  (Anchor **)NULL, NULL, NULL, '\0',
				  currentLn(buf), pos);
#endif				/* ID_EXT */
	    }
	}
	/* end of processing for one line */
	if (!internal)
	    addnewline(buf, outc, outp, NULL, pos, -1, nlines);
	if (internal == HTML_N_INTERNAL)
	    internal = 0;
	if (str != endp) {
	    line = Strsubstr(line, str - line->ptr, endp - str);
	    goto proc_again;
	}
    }
#ifdef DEBUG
    if (w3m_debug)
	fclose(debug);
#endif
    for (form_id = 1; form_id <= form_max; form_id++)
	forms[form_id]->next = forms[form_id - 1];
    buf->formlist = (form_max >= 0) ? forms[form_max] : NULL;
    if (n_textarea)
	addMultirowsForm(buf, buf->formitem);
#ifdef USE_IMAGE
    addMultirowsImg(buf, buf->img);
#endif
}

static void
addLink(Buffer *buf, struct parsed_tag *tag)
{
    char *href = NULL, *title = NULL, *ctype = NULL, *rel = NULL, *rev = NULL;
    char type = LINK_TYPE_NONE;
    LinkList *l;

    parsedtag_get_value(tag, ATTR_HREF, &href);
    if (href)
	href = url_quote_conv(remove_space(href), buf->document_charset);
    parsedtag_get_value(tag, ATTR_TITLE, &title);
    parsedtag_get_value(tag, ATTR_TYPE, &ctype);
    parsedtag_get_value(tag, ATTR_REL, &rel);
    if (rel != NULL) {
	/* forward link type */
	type = LINK_TYPE_REL;
	if (title == NULL)
	    title = rel;
    }
    parsedtag_get_value(tag, ATTR_REV, &rev);
    if (rev != NULL) {
	/* reverse link type */
	type = LINK_TYPE_REV;
	if (title == NULL)
	    title = rev;
    }

    l = New(LinkList);
    l->url = href;
    l->title = title;
    l->ctype = ctype;
    l->type = type;
    l->next = NULL;
    if (buf->linklist) {
	LinkList *i;
	for (i = buf->linklist; i->next; i = i->next) ;
	i->next = l;
    }
    else
	buf->linklist = l;
}

void
HTMLlineproc2(Buffer *buf, TextLineList *tl)
{
    _tl_lp2 = tl->first;
    HTMLlineproc2body(buf, textlist_feed, -1);
}

static InputStream _file_lp2;

static Str
file_feed()
{
    Str s;
    s = StrISgets(_file_lp2);
    if (s->length == 0) {
	ISclose(_file_lp2);
	return NULL;
    }
    return s;
}

void
HTMLlineproc3(Buffer *buf, InputStream stream)
{
    _file_lp2 = stream;
    HTMLlineproc2body(buf, file_feed, -1);
}

static void
proc_escape(struct readbuffer *obuf, char **str_return)
{
    char *str = *str_return, *estr;
    int ech = getescapechar(str_return);
    int width, n_add = *str_return - str;
    Lineprop mode = PC_ASCII;

    if (ech < 0) {
	*str_return = str;
	proc_mchar(obuf, obuf->flag & RB_SPECIAL, 1, str_return, PC_ASCII);
	return;
    }
    mode = IS_CNTRL(ech) ? PC_CTRL : PC_ASCII;

    estr = conv_entity(ech);
    check_breakpoint(obuf, obuf->flag & RB_SPECIAL, estr);
    width = get_strwidth(estr);
    if (width == 1 && ech == (unsigned char)*estr &&
	ech != '&' && ech != '<' && ech != '>') {
	if (IS_CNTRL(ech))
	    mode = PC_CTRL;
	push_charp(obuf, width, estr, mode);
    }
    else
	push_nchars(obuf, width, str, n_add, mode);
    set_prevchar(obuf->prevchar, estr, strlen(estr));
    obuf->prev_ctype = mode;
}


static int
need_flushline(struct html_feed_environ *h_env, struct readbuffer *obuf,
	       Lineprop mode)
{
    char ch;

    if (obuf->flag & RB_PRE_INT) {
	if (obuf->pos > h_env->limit)
	    return 1;
	else
	    return 0;
    }

    ch = Strlastchar(obuf->line);
    /* if (ch == ' ' && obuf->tag_sp > 0) */
    if (ch == ' ')
	return 0;

    if (obuf->pos > h_env->limit)
	return 1;

    return 0;
}

static int
table_width(struct html_feed_environ *h_env, int table_level)
{
    int width;
    if (table_level < 0)
	return 0;
    width = tables[table_level]->total_width;
    if (table_level > 0 || width > 0)
	return width;
    return h_env->limit - h_env->envs[h_env->envc].indent;
}

/* HTML processing first pass */
void
HTMLlineproc0(char *line, struct html_feed_environ *h_env, int internal)
{
    Lineprop mode;
    int cmd;
    struct readbuffer *obuf = h_env->obuf;
    int indent, delta;
    struct parsed_tag *tag;
    Str tokbuf;
    struct table *tbl = NULL;
    struct table_mode *tbl_mode = NULL;
    int tbl_width = 0;
#ifdef USE_M17N
    int is_hangul, prev_is_hangul = 0;
#endif

#ifdef DEBUG
    if (w3m_debug) {
	FILE *f = fopen("zzzproc1", "a");
	fprintf(f, "%c%c%c%c",
		(obuf->flag & RB_PREMODE) ? 'P' : ' ',
		(obuf->table_level >= 0) ? 'T' : ' ',
		(obuf->flag & RB_INTXTA) ? 'X' : ' ',
		(obuf->flag & (RB_SCRIPT | RB_STYLE)) ? 'S' : ' ');
	fprintf(f, "HTMLlineproc1(\"%s\",%d,%lx)\n", line, h_env->limit,
		(unsigned long)h_env);
	fclose(f);
    }
#endif

    tokbuf = Strnew();

  table_start:
    if (obuf->table_level >= 0) {
	int level = min(obuf->table_level, MAX_TABLE - 1);
	tbl = tables[level];
	tbl_mode = &table_mode[level];
	tbl_width = table_width(h_env, level);
    }

    while (*line != '\0') {
	char *str, *p;
	int is_tag = FALSE;
	int pre_mode = (obuf->table_level >= 0) ? tbl_mode->pre_mode :
	    obuf->flag;
	int end_tag = (obuf->table_level >= 0) ? tbl_mode->end_tag :
	    obuf->end_tag;

	if (*line == '<' || obuf->status != R_ST_NORMAL) {
	    /* 
	     * Tag processing
	     */
	    if (obuf->status == R_ST_EOL)
		obuf->status = R_ST_NORMAL;
	    else {
		read_token(h_env->tagbuf, &line, &obuf->status,
			   pre_mode & RB_PREMODE, obuf->status != R_ST_NORMAL);
		if (obuf->status != R_ST_NORMAL)
		    return;
	    }
	    if (h_env->tagbuf->length == 0)
		continue;
	    str = h_env->tagbuf->ptr;
	    if (*str == '<') {
		if (str[1] && REALLY_THE_BEGINNING_OF_A_TAG(str))
		    is_tag = TRUE;
		else if (!(pre_mode & (RB_PLAIN | RB_INTXTA | RB_INSELECT |
				       RB_SCRIPT | RB_STYLE | RB_TITLE))) {
		    line = Strnew_m_charp(str + 1, line, NULL)->ptr;
		    str = "&lt;";
		}
	    }
	}
	else {
	    read_token(tokbuf, &line, &obuf->status, pre_mode & RB_PREMODE, 0);
	    if (obuf->status != R_ST_NORMAL)	/* R_ST_AMP ? */
		obuf->status = R_ST_NORMAL;
	    str = tokbuf->ptr;
	}

	if (pre_mode & (RB_PLAIN | RB_INTXTA | RB_INSELECT | RB_SCRIPT |
			RB_STYLE | RB_TITLE)) {
	    if (is_tag) {
		p = str;
		if ((tag = parse_tag(&p, internal))) {
		    if (tag->tagid == end_tag ||
			(pre_mode & RB_INSELECT && tag->tagid == HTML_N_FORM)
			|| (pre_mode & RB_TITLE
			    && (tag->tagid == HTML_N_HEAD
				|| tag->tagid == HTML_BODY)))
			goto proc_normal;
		}
	    }
	    /* title */
	    if (pre_mode & RB_TITLE) {
		feed_title(str);
		continue;
	    }
	    /* select */
	    if (pre_mode & RB_INSELECT) {
		if (obuf->table_level >= 0)
		    goto proc_normal;
		feed_select(str);
		continue;
	    }
	    if (is_tag) {
		if (strncmp(str, "<!--", 4) && (p = strchr(str + 1, '<'))) {
		    str = Strnew_charp_n(str, p - str)->ptr;
		    line = Strnew_m_charp(p, line, NULL)->ptr;
		}
		is_tag = FALSE;
	    }
	    if (obuf->table_level >= 0)
		goto proc_normal;
	    /* textarea */
	    if (pre_mode & RB_INTXTA) {
		feed_textarea(str);
		continue;
	    }
	    /* script */
	    if (pre_mode & RB_SCRIPT)
		continue;
	    /* style */
	    if (pre_mode & RB_STYLE)
		continue;
	}

      proc_normal:
	if (obuf->table_level >= 0) {
	    /* 
	     * within table: in <table>..</table>, all input tokens
	     * are fed to the table renderer, and then the renderer
	     * makes HTML output.
	     */
	    switch (feed_table(tbl, str, tbl_mode, tbl_width, internal)) {
	    case 0:
		/* </table> tag */
		obuf->table_level--;
		if (obuf->table_level >= MAX_TABLE - 1)
		    continue;
		end_table(tbl);
		if (obuf->table_level >= 0) {
		    struct table *tbl0 = tables[obuf->table_level];
		    str = Sprintf("<table_alt tid=%d>", tbl0->ntable)->ptr;
		    pushTable(tbl0, tbl);
		    tbl = tbl0;
		    tbl_mode = &table_mode[obuf->table_level];
		    tbl_width = table_width(h_env, obuf->table_level);
		    feed_table(tbl, str, tbl_mode, tbl_width, TRUE);
		    continue;
		    /* continue to the next */
		}
		if (obuf->flag & RB_DEL)
		    continue;
		/* all tables have been read */
		if (tbl->vspace > 0 && !(obuf->flag & RB_IGNORE_P)) {
		    int indent = h_env->envs[h_env->envc].indent;
		    flushline(h_env, obuf, indent, 0, h_env->limit);
		    do_blankline(h_env, obuf, indent, 0, h_env->limit);
		}
		save_fonteffect(h_env, obuf);
		renderTable(tbl, tbl_width, h_env);
		restore_fonteffect(h_env, obuf);
		obuf->flag &= ~RB_IGNORE_P;
		if (tbl->vspace > 0) {
		    int indent = h_env->envs[h_env->envc].indent;
		    do_blankline(h_env, obuf, indent, 0, h_env->limit);
		    obuf->flag |= RB_IGNORE_P;
		}
		set_space_to_prevchar(obuf->prevchar);
		continue;
	    case 1:
		/* <table> tag */
		break;
	    default:
		continue;
	    }
	}

	if (is_tag) {
/*** Beginning of a new tag ***/
	    if ((tag = parse_tag(&str, internal)))
		cmd = tag->tagid;
	    else
		continue;
	    /* process tags */
	    if (HTMLtagproc1(tag, h_env) == 0) {
		/* preserve the tag for second-stage processing */
		if (parsedtag_need_reconstruct(tag))
		    h_env->tagbuf = parsedtag2str(tag);
		push_tag(obuf, h_env->tagbuf->ptr, cmd);
	    }
#ifdef ID_EXT
	    else {
		process_idattr(obuf, cmd, tag);
	    }
#endif				/* ID_EXT */
	    obuf->bp.init_flag = 1;
	    clear_ignore_p_flag(cmd, obuf);
	    if (cmd == HTML_TABLE)
		goto table_start;
	    else
		continue;
	}

	if (obuf->flag & (RB_DEL | RB_S))
	    continue;
	while (*str) {
	    mode = get_mctype(str);
	    delta = get_mcwidth(str);
	    if (obuf->flag & (RB_SPECIAL & ~RB_NOBR)) {
		char ch = *str;
		if (!(obuf->flag & RB_PLAIN) && (*str == '&')) {
		    char *p = str;
		    int ech = getescapechar(&p);
		    if (ech == '\n' || ech == '\r') {
			ch = '\n';
			str = p - 1;
		    }
		    else if (ech == '\t') {
			ch = '\t';
			str = p - 1;
		    }
		}
		if (ch != '\n')
		    obuf->flag &= ~RB_IGNORE_P;
		if (ch == '\n') {
		    str++;
		    if (obuf->flag & RB_IGNORE_P) {
			obuf->flag &= ~RB_IGNORE_P;
			continue;
		    }
		    if (obuf->flag & RB_PRE_INT)
			PUSH(' ');
		    else
			flushline(h_env, obuf, h_env->envs[h_env->envc].indent,
				  1, h_env->limit);
		}
		else if (ch == '\t') {
		    do {
			PUSH(' ');
		    } while ((h_env->envs[h_env->envc].indent + obuf->pos)
			     % Tabstop != 0);
		    str++;
		}
		else if (obuf->flag & RB_PLAIN) {
		    char *p = html_quote_char(*str);
		    if (p) {
			push_charp(obuf, 1, p, PC_ASCII);
			str++;
		    }
		    else {
			proc_mchar(obuf, 1, delta, &str, mode);
		    }
		}
		else {
		    if (*str == '&')
			proc_escape(obuf, &str);
		    else
			proc_mchar(obuf, 1, delta, &str, mode);
		}
		if (obuf->flag & (RB_SPECIAL & ~RB_PRE_INT))
		    continue;
	    }
	    else {
		if (!IS_SPACE(*str))
		    obuf->flag &= ~RB_IGNORE_P;
		if ((mode == PC_ASCII || mode == PC_CTRL) && IS_SPACE(*str)) {
		    if (*obuf->prevchar->ptr != ' ') {
			PUSH(' ');
		    }
		    str++;
		}
		else {
#ifdef USE_M17N
		    if (mode == PC_KANJI1)
			is_hangul = wtf_is_hangul((wc_uchar *) str);
		    else
			is_hangul = 0;
		    if (!SimplePreserveSpace && mode == PC_KANJI1 &&
			!is_hangul && !prev_is_hangul &&
			obuf->pos > h_env->envs[h_env->envc].indent &&
			Strlastchar(obuf->line) == ' ') {
			while (obuf->line->length >= 2 &&
			       !strncmp(obuf->line->ptr + obuf->line->length -
					2, "  ", 2)
			       && obuf->pos >= h_env->envs[h_env->envc].indent) {
			    Strshrink(obuf->line, 1);
			    obuf->pos--;
			}
			if (obuf->line->length >= 3 &&
			    obuf->prev_ctype == PC_KANJI1 &&
			    Strlastchar(obuf->line) == ' ' &&
			    obuf->pos >= h_env->envs[h_env->envc].indent) {
			    Strshrink(obuf->line, 1);
			    obuf->pos--;
			}
		    }
		    prev_is_hangul = is_hangul;
#endif
		    if (*str == '&')
			proc_escape(obuf, &str);
		    else
			proc_mchar(obuf, obuf->flag & RB_SPECIAL, delta, &str,
				   mode);
		}
	    }
	    if (need_flushline(h_env, obuf, mode)) {
		char *bp = obuf->line->ptr + obuf->bp.len;
		char *tp = bp - obuf->bp.tlen;
		int i = 0;

		if (tp > obuf->line->ptr && tp[-1] == ' ')
		    i = 1;

		indent = h_env->envs[h_env->envc].indent;
		if (obuf->bp.pos - i > indent) {
		    Str line;
		    append_tags(obuf);
		    line = Strnew_charp(bp);
		    Strshrink(obuf->line, obuf->line->length - obuf->bp.len);
#ifdef FORMAT_NICE
		    if (obuf->pos - i > h_env->limit)
			obuf->flag |= RB_FILL;
#endif				/* FORMAT_NICE */
		    back_to_breakpoint(obuf);
		    flushline(h_env, obuf, indent, 0, h_env->limit);
#ifdef FORMAT_NICE
		    obuf->flag &= ~RB_FILL;
#endif				/* FORMAT_NICE */
		    HTMLlineproc1(line->ptr, h_env);
		}
	    }
	}
    }
    if (!(obuf->flag & (RB_SPECIAL | RB_INTXTA | RB_INSELECT))) {
	char *tp;
	int i = 0;

	if (obuf->bp.pos == obuf->pos) {
	    tp = &obuf->line->ptr[obuf->bp.len - obuf->bp.tlen];
	}
	else {
	    tp = &obuf->line->ptr[obuf->line->length];
	}

	if (tp > obuf->line->ptr && tp[-1] == ' ')
	    i = 1;
	indent = h_env->envs[h_env->envc].indent;
	if (obuf->pos - i > h_env->limit) {
#ifdef FORMAT_NICE
	    obuf->flag |= RB_FILL;
#endif				/* FORMAT_NICE */
	    flushline(h_env, obuf, indent, 0, h_env->limit);
#ifdef FORMAT_NICE
	    obuf->flag &= ~RB_FILL;
#endif				/* FORMAT_NICE */
	}
    }
}

extern char *NullLine;
extern Lineprop NullProp[];

#ifndef USE_ANSI_COLOR
#define addnewline2(a,b,c,d,e,f) _addnewline2(a,b,c,e,f)
#endif
static void
addnewline2(Buffer *buf, char *line, Lineprop *prop, Linecolor *color, int pos,
	    int nlines)
{
    Line *l;
    l = New(Line);
    l->next = NULL;
    l->lineBuf = line;
    l->propBuf = prop;
#ifdef USE_ANSI_COLOR
    l->colorBuf = color;
#endif
    l->len = pos;
    l->width = -1;
    l->size = pos;
    l->bpos = 0;
    l->bwidth = 0;
    l->prev = buf->currentLine;
    if (buf->currentLine) {
	l->next = buf->currentLine->next;
	buf->currentLine->next = l;
    }
    else
	l->next = NULL;
    if (buf->lastLine == NULL || buf->lastLine == buf->currentLine)
	buf->lastLine = l;
    buf->currentLine = l;
    if (buf->firstLine == NULL)
	buf->firstLine = l;
    l->linenumber = ++buf->allLine;
    if (nlines < 0) {
	/*     l->real_linenumber = l->linenumber;     */
	l->real_linenumber = 0;
    }
    else {
	l->real_linenumber = nlines;
    }
    l = NULL;
}

static void
addnewline(Buffer *buf, char *line, Lineprop *prop, Linecolor *color, int pos,
	   int width, int nlines)
{
    char *s;
    Lineprop *p;
#ifdef USE_ANSI_COLOR
    Linecolor *c;
#endif
    Line *l;
    int i, bpos, bwidth;

    if (pos > 0) {
	s = allocStr(line, pos);
	p = NewAtom_N(Lineprop, pos);
	bcopy((void *)prop, (void *)p, pos * sizeof(Lineprop));
    }
    else {
	s = NullLine;
	p = NullProp;
    }
#ifdef USE_ANSI_COLOR
    if (pos > 0 && color) {
	c = NewAtom_N(Linecolor, pos);
	bcopy((void *)color, (void *)c, pos * sizeof(Linecolor));
    }
    else {
	c = NULL;
    }
#endif
    addnewline2(buf, s, p, c, pos, nlines);
    if (pos <= 0 || width <= 0)
	return;
    bpos = 0;
    bwidth = 0;
    while (1) {
	l = buf->currentLine;
	l->bpos = bpos;
	l->bwidth = bwidth;
	i = columnLen(l, width);
	if (i == 0) {
	    i++;
#ifdef USE_M17N
	    while (i < l->len && p[i] & PC_WCHAR2)
		i++;
#endif
	}
	l->len = i;
	l->width = COLPOS(l, l->len);
	if (pos <= i)
	    return;
	bpos += l->len;
	bwidth += l->width;
	s += i;
	p += i;
#ifdef USE_ANSI_COLOR
	if (c)
	    c += i;
#endif
	pos -= i;
	addnewline2(buf, s, p, c, pos, nlines);
    }
}

/* 
 * loadHTMLBuffer: read file and make new buffer
 */
Buffer *
loadHTMLBuffer(URLFile *f, Buffer *newBuf)
{
    FILE *src = NULL;
    Str tmp;

    if (newBuf == NULL)
	newBuf = newBuffer(INIT_BUFFER_WIDTH);
    if (newBuf->sourcefile == NULL &&
	(f->scheme != SCM_LOCAL || newBuf->mailcap)) {
	tmp = tmpfname(TMPF_SRC, ".html");
	src = fopen(tmp->ptr, "w");
	if (src)
	    newBuf->sourcefile = tmp->ptr;
    }

    loadHTMLstream(f, newBuf, src, newBuf->bufferprop & BP_FRAME);

    newBuf->topLine = newBuf->firstLine;
    newBuf->lastLine = newBuf->currentLine;
    newBuf->currentLine = newBuf->firstLine;
    if (n_textarea)
	formResetBuffer(newBuf, newBuf->formitem);
    if (src)
	fclose(src);

    return newBuf;
}

static char *_size_unit[] = { "b", "kb", "Mb", "Gb", "Tb",
    "Pb", "Eb", "Zb", "Bb", "Yb", NULL
};

char *
convert_size(clen_t size, int usefloat)
{
    float csize;
    int sizepos = 0;
    char **sizes = _size_unit;

    csize = (float)size;
    while (csize >= 999.495 && sizes[sizepos + 1]) {
	csize = csize / 1024.0;
	sizepos++;
    }
    return Sprintf(usefloat ? "%.3g%s" : "%.0f%s",
		   floor(csize * 100.0 + 0.5) / 100.0, sizes[sizepos])->ptr;
}

char *
convert_size2(clen_t size1, clen_t size2, int usefloat)
{
    char **sizes = _size_unit;
    float csize, factor = 1;
    int sizepos = 0;

    csize = (float)((size1 > size2) ? size1 : size2);
    while (csize / factor >= 999.495 && sizes[sizepos + 1]) {
	factor *= 1024.0;
	sizepos++;
    }
    return Sprintf(usefloat ? "%.3g/%.3g%s" : "%.0f/%.0f%s",
		   floor(size1 / factor * 100.0 + 0.5) / 100.0,
		   floor(size2 / factor * 100.0 + 0.5) / 100.0,
		   sizes[sizepos])->ptr;
}

void
showProgress(clen_t * linelen, clen_t * trbyte)
{
    int i, j, rate, duration, eta, pos;
    static time_t last_time, start_time;
    time_t cur_time;
    Str messages;
    char *fmtrbyte, *fmrate;

    if (!fmInitialized)
	return;

    if (*linelen < 1024)
	return;
    if (current_content_length > 0) {
	double ratio;
	cur_time = time(0);
	if (*trbyte == 0) {
	    move(LASTLINE, 0);
	    clrtoeolx();
	    start_time = cur_time;
	}
	*trbyte += *linelen;
	*linelen = 0;
	if (cur_time == last_time)
	    return;
	last_time = cur_time;
	move(LASTLINE, 0);
	ratio = 100.0 * (*trbyte) / current_content_length;
	fmtrbyte = convert_size2(*trbyte, current_content_length, 1);
	duration = cur_time - start_time;
	if (duration) {
	    rate = *trbyte / duration;
	    fmrate = convert_size(rate, 1);
	    eta = rate ? (current_content_length - *trbyte) / rate : -1;
	    messages = Sprintf("%11s %3.0f%% "
			       "%7s/s "
			       "eta %02d:%02d:%02d     ",
			       fmtrbyte, ratio,
			       fmrate,
			       eta / (60 * 60), (eta / 60) % 60, eta % 60);
	}
	else {
	    messages = Sprintf("%11s %3.0f%%                          ",
			       fmtrbyte, ratio);
	}
	addstr(messages->ptr);
	pos = 42;
	i = pos + (COLS - pos - 1) * (*trbyte) / current_content_length;
	move(LASTLINE, pos);
	standout();
	addch(' ');
	for (j = pos + 1; j <= i; j++)
	    addch('|');
	standend();
	/* no_clrtoeol(); */
	refresh();
    }
    else {
	cur_time = time(0);
	if (*trbyte == 0) {
	    move(LASTLINE, 0);
	    clrtoeolx();
	    start_time = cur_time;
	}
	*trbyte += *linelen;
	*linelen = 0;
	if (cur_time == last_time)
	    return;
	last_time = cur_time;
	move(LASTLINE, 0);
	fmtrbyte = convert_size(*trbyte, 1);
	duration = cur_time - start_time;
	if (duration) {
	    fmrate = convert_size(*trbyte / duration, 1);
	    messages = Sprintf("%7s loaded %7s/s", fmtrbyte, fmrate);
	}
	else {
	    messages = Sprintf("%7s loaded", fmtrbyte);
	}
	message(messages->ptr, 0, 0);
	refresh();
    }
}

void
init_henv(struct html_feed_environ *h_env, struct readbuffer *obuf,
	  struct environment *envs, int nenv, TextLineList *buf,
	  int limit, int indent)
{
    envs[0].indent = indent;

    obuf->line = Strnew();
    obuf->cprop = 0;
    obuf->pos = 0;
    obuf->prevchar = Strnew_size(8);
    set_space_to_prevchar(obuf->prevchar);
    obuf->flag = RB_IGNORE_P;
    obuf->flag_sp = 0;
    obuf->status = R_ST_NORMAL;
    obuf->table_level = -1;
    obuf->nobr_level = 0;
    bzero((void *)&obuf->anchor, sizeof(obuf->anchor));
    obuf->img_alt = 0;
    obuf->in_bold = 0;
    obuf->in_italic = 0;
    obuf->in_under = 0;
    obuf->in_strike = 0;
    obuf->in_ins = 0;
    obuf->prev_ctype = PC_ASCII;
    obuf->tag_sp = 0;
    obuf->fontstat_sp = 0;
    obuf->top_margin = 0;
    obuf->bottom_margin = 0;
    obuf->bp.init_flag = 1;
    set_breakpoint(obuf, 0);

    h_env->buf = buf;
    h_env->f = NULL;
    h_env->obuf = obuf;
    h_env->tagbuf = Strnew();
    h_env->limit = limit;
    h_env->maxlimit = 0;
    h_env->envs = envs;
    h_env->nenv = nenv;
    h_env->envc = 0;
    h_env->envc_real = 0;
    h_env->title = NULL;
    h_env->blank_lines = 0;
}

void
completeHTMLstream(struct html_feed_environ *h_env, struct readbuffer *obuf)
{
    close_anchor(h_env, obuf);
    if (obuf->img_alt) {
	push_tag(obuf, "</img_alt>", HTML_N_IMG_ALT);
	obuf->img_alt = NULL;
    }
    if (obuf->in_bold) {
	push_tag(obuf, "</b>", HTML_N_B);
	obuf->in_bold = 0;
    }
    if (obuf->in_italic) {
	push_tag(obuf, "</i>", HTML_N_I);
	obuf->in_italic = 0;
    }
    if (obuf->in_under) {
	push_tag(obuf, "</u>", HTML_N_U);
	obuf->in_under = 0;
    }
    if (obuf->in_strike) {
	push_tag(obuf, "</s>", HTML_N_S);
	obuf->in_strike = 0;
    }
    if (obuf->in_ins) {
	push_tag(obuf, "</ins>", HTML_N_INS);
	obuf->in_ins = 0;
    }
    if (obuf->flag & RB_INTXTA)
	HTMLlineproc1("</textarea>", h_env);
    /* for unbalanced select tag */
    if (obuf->flag & RB_INSELECT)
	HTMLlineproc1("</select>", h_env);
    if (obuf->flag & RB_TITLE)
	HTMLlineproc1("</title>", h_env);

    /* for unbalanced table tag */
    if (obuf->table_level >= MAX_TABLE)
	obuf->table_level = MAX_TABLE - 1;

    while (obuf->table_level >= 0) {
	table_mode[obuf->table_level].pre_mode
	    &= ~(TBLM_SCRIPT | TBLM_STYLE | TBLM_PLAIN);
	HTMLlineproc1("</table>", h_env);
    }
}

static void
print_internal_information(struct html_feed_environ *henv)
{
    int i;
    Str s;
    TextLineList *tl = newTextLineList();

    s = Strnew_charp("<internal>");
    pushTextLine(tl, newTextLine(s, 0));
    if (henv->title) {
	s = Strnew_m_charp("<title_alt title=\"",
			   html_quote(henv->title), "\">", NULL);
	pushTextLine(tl, newTextLine(s, 0));
    }
#if 0
    if (form_max >= 0) {
	FormList *fp;
	for (i = 0; i <= form_max; i++) {
	    fp = forms[i];
	    s = Sprintf("<form_int fid=\"%d\" action=\"%s\" method=\"%s\"",
			i, html_quote(fp->action->ptr),
			(fp->method == FORM_METHOD_POST) ? "post"
			: ((fp->method ==
			    FORM_METHOD_INTERNAL) ? "internal" : "get"));
	    if (fp->target)
		Strcat(s, Sprintf(" target=\"%s\"", html_quote(fp->target)));
	    if (fp->enctype == FORM_ENCTYPE_MULTIPART)
		Strcat_charp(s, " enctype=\"multipart/form-data\"");
#ifdef USE_M17N
	    if (fp->charset)
		Strcat(s, Sprintf(" accept-charset=\"%s\"",
				  html_quote(fp->charset)));
#endif
	    Strcat_charp(s, ">");
	    pushTextLine(tl, newTextLine(s, 0));
	}
    }
#endif
#ifdef MENU_SELECT
    if (n_select > 0) {
	FormSelectOptionItem *ip;
	for (i = 0; i < n_select; i++) {
	    s = Sprintf("<select_int selectnumber=%d>", i);
	    pushTextLine(tl, newTextLine(s, 0));
	    for (ip = select_option[i].first; ip; ip = ip->next) {
		s = Sprintf("<option_int value=\"%s\" label=\"%s\"%s>",
			    html_quote(ip->value ? ip->value->ptr :
				       ip->label->ptr),
			    html_quote(ip->label->ptr),
			    ip->checked ? " selected" : "");
		pushTextLine(tl, newTextLine(s, 0));
	    }
	    s = Strnew_charp("</select_int>");
	    pushTextLine(tl, newTextLine(s, 0));
	}
    }
#endif				/* MENU_SELECT */
    if (n_textarea > 0) {
	for (i = 0; i < n_textarea; i++) {
	    s = Sprintf("<textarea_int textareanumber=%d>", i);
	    pushTextLine(tl, newTextLine(s, 0));
	    s = Strnew_charp(html_quote(textarea_str[i]->ptr));
	    Strcat_charp(s, "</textarea_int>");
	    pushTextLine(tl, newTextLine(s, 0));
	}
    }
    s = Strnew_charp("</internal>");
    pushTextLine(tl, newTextLine(s, 0));

    if (henv->buf)
	appendTextLineList(henv->buf, tl);
    else if (henv->f) {
	TextLineListItem *p;
	for (p = tl->first; p; p = p->next)
	    fprintf(henv->f, "%s\n", Str_conv_to_halfdump(p->ptr->line)->ptr);
    }
}

void
loadHTMLstream(URLFile *f, Buffer *newBuf, FILE * src, int internal)
{
    struct environment envs[MAX_ENV_LEVEL];
    clen_t linelen = 0;
    clen_t trbyte = 0;
    Str lineBuf2 = Strnew();
#ifdef USE_M17N
    wc_ces charset = WC_CES_US_ASCII;
    wc_ces volatile doc_charset = DocumentCharset;
#endif
    struct html_feed_environ htmlenv1;
    struct readbuffer obuf;
#ifdef USE_IMAGE
    int volatile image_flag;
#endif
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;

#ifdef USE_M17N
    if (fmInitialized && graph_ok()) {
	symbol_width = symbol_width0 = 1;
    }
    else {
	symbol_width0 = 0;
	get_symbol(DisplayCharset, &symbol_width0);
	symbol_width = WcOption.use_wide ? symbol_width0 : 1;
    }
#else
    symbol_width = symbol_width0 = 1;
#endif

    cur_title = NULL;
    n_textarea = 0;
    cur_textarea = NULL;
    max_textarea = MAX_TEXTAREA;
    textarea_str = New_N(Str, max_textarea);
#ifdef MENU_SELECT
    n_select = 0;
    max_select = MAX_SELECT;
    select_option = New_N(FormSelectOption, max_select);
#endif				/* MENU_SELECT */
    cur_select = NULL;
    form_sp = -1;
    form_max = -1;
    forms_size = 0;
    forms = NULL;
    cur_hseq = 1;
#ifdef USE_IMAGE
    cur_iseq = 1;
    if (newBuf->image_flag)
	image_flag = newBuf->image_flag;
    else if (activeImage && displayImage && autoImage)
	image_flag = IMG_FLAG_AUTO;
    else
	image_flag = IMG_FLAG_SKIP;
    if (newBuf->currentURL.file)
	cur_baseURL = baseURL(newBuf);
#endif

    if (w3m_halfload) {
	newBuf->buffername = "---";
#ifdef USE_M17N
	newBuf->document_charset = InnerCharset;
#endif
	max_textarea = 0;
#ifdef MENU_SELECT
	max_select = 0;
#endif
	HTMLlineproc3(newBuf, f->stream);
	w3m_halfload = FALSE;
	return;
    }

    init_henv(&htmlenv1, &obuf, envs, MAX_ENV_LEVEL, NULL, newBuf->width, 0);

    if (w3m_halfdump)
	htmlenv1.f = stdout;
    else
	htmlenv1.buf = newTextLineList();

    if (SETJMP(AbortLoading) != 0) {
	HTMLlineproc1("<br>Transfer Interrupted!<br>", &htmlenv1);
	goto phase2;
    }
    TRAP_ON;

#ifdef USE_M17N
    if (newBuf != NULL) {
	if (newBuf->bufferprop & BP_FRAME)
	    charset = InnerCharset;
	else if (newBuf->document_charset)
	    charset = doc_charset = newBuf->document_charset;
    }
    if (content_charset && UseContentCharset)
	doc_charset = content_charset;
    else if (f->guess_type && !strcasecmp(f->guess_type, "application/xhtml+xml"))
	doc_charset = WC_CES_UTF_8;
    meta_charset = 0;
#endif
#if	0
    do_blankline(&htmlenv1, &obuf, 0, 0, htmlenv1.limit);
    obuf.flag = RB_IGNORE_P;
#endif
    if (IStype(f->stream) != IST_ENCODED)
	f->stream = newEncodedStream(f->stream, f->encoding);
    while ((lineBuf2 = StrmyUFgets(f))->length) {
#ifdef USE_NNTP
	if (f->scheme == SCM_NEWS && lineBuf2->ptr[0] == '.') {
	    Strshrinkfirst(lineBuf2, 1);
	    if (lineBuf2->ptr[0] == '\n' || lineBuf2->ptr[0] == '\r' ||
		lineBuf2->ptr[0] == '\0') {
		/*
		 * iseos(f->stream) = TRUE;
		 */
		break;
	    }
	}
#endif				/* USE_NNTP */
	if (src)
	    Strfputs(lineBuf2, src);
	linelen += lineBuf2->length;
	if (w3m_dump & DUMP_EXTRA)
	    printf("W3m-in-progress: %s\n", convert_size2(linelen, current_content_length, TRUE));
	if (w3m_dump & DUMP_SOURCE)
	    continue;
	showProgress(&linelen, &trbyte);
	/*
	 * if (frame_source)
	 * continue;
	 */
#ifdef USE_M17N
	if (meta_charset) {	/* <META> */
	    if (content_charset == 0 && UseContentCharset) {
		doc_charset = meta_charset;
		charset = WC_CES_US_ASCII;
	    }
	    meta_charset = 0;
	}
#endif
	lineBuf2 = convertLine(f, lineBuf2, HTML_MODE, &charset, doc_charset);
#if defined(USE_M17N) && defined(USE_IMAGE)
	cur_document_charset = charset;
#endif
	HTMLlineproc0(lineBuf2->ptr, &htmlenv1, internal);
    }
    if (obuf.status != R_ST_NORMAL) {
	obuf.status = R_ST_EOL;
	HTMLlineproc0("\n", &htmlenv1, internal);
    }
    obuf.status = R_ST_NORMAL;
    completeHTMLstream(&htmlenv1, &obuf);
    flushline(&htmlenv1, &obuf, 0, 2, htmlenv1.limit);
    if (htmlenv1.title)
	newBuf->buffername = htmlenv1.title;
    if (w3m_halfdump) {
	TRAP_OFF;
	print_internal_information(&htmlenv1);
	return;
    }
    if (w3m_backend) {
	TRAP_OFF;
	print_internal_information(&htmlenv1);
	backend_halfdump_buf = htmlenv1.buf;
	return;
    }
  phase2:
    newBuf->trbyte = trbyte + linelen;
    TRAP_OFF;
#ifdef USE_M17N
    if (!(newBuf->bufferprop & BP_FRAME))
	newBuf->document_charset = charset;
#endif
#ifdef USE_IMAGE
    newBuf->image_flag = image_flag;
#endif
    HTMLlineproc2(newBuf, htmlenv1.buf);
}

/* 
 * loadHTMLString: read string and make new buffer
 */
Buffer *
loadHTMLString(Str page)
{
    URLFile f;
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
    Buffer *newBuf;

    newBuf = newBuffer(INIT_BUFFER_WIDTH);
    if (SETJMP(AbortLoading) != 0) {
	TRAP_OFF;
	discardBuffer(newBuf);
	return NULL;
    }
    TRAP_ON;

    init_stream(&f, SCM_LOCAL, newStrStream(page));

#ifdef USE_M17N
    newBuf->document_charset = InnerCharset;
#endif
    loadHTMLstream(&f, newBuf, NULL, TRUE);
#ifdef USE_M17N
    newBuf->document_charset = WC_CES_US_ASCII;
#endif

    TRAP_OFF;
    newBuf->topLine = newBuf->firstLine;
    newBuf->lastLine = newBuf->currentLine;
    newBuf->currentLine = newBuf->firstLine;
    newBuf->type = "text/html";
    newBuf->real_type = newBuf->type;
    if (n_textarea)
	formResetBuffer(newBuf, newBuf->formitem);
    return newBuf;
}

#ifdef USE_GOPHER

/* 
 * loadGopherDir: get gopher directory
 */
Str
loadGopherDir(URLFile *uf, ParsedURL *pu, wc_ces * charset)
{
    Str volatile tmp;
    Str lbuf, name, file, host, port;
    char *volatile p, *volatile q;
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
#ifdef USE_M17N
    wc_ces doc_charset = DocumentCharset;
#endif

    tmp = parsedURL2Str(pu);
    p = html_quote(tmp->ptr);
    tmp =
	convertLine(NULL, Strnew_charp(file_unquote(tmp->ptr)), RAW_MODE,
		    charset, doc_charset);
    q = html_quote(tmp->ptr);
    tmp = Strnew_m_charp("<html>\n<head>\n<base href=\"", p, "\">\n<title>", q,
			 "</title>\n</head>\n<body>\n<h1>Index of ", q,
			 "</h1>\n<table>\n", NULL);

    if (SETJMP(AbortLoading) != 0)
	goto gopher_end;
    TRAP_ON;

    while (1) {
	if (lbuf = StrUFgets(uf), lbuf->length == 0)
	    break;
	if (lbuf->ptr[0] == '.' &&
	    (lbuf->ptr[1] == '\n' || lbuf->ptr[1] == '\r'))
	    break;
	lbuf = convertLine(uf, lbuf, HTML_MODE, charset, doc_charset);
	p = lbuf->ptr;
	for (q = p; *q && *q != '\t'; q++) ;
	name = Strnew_charp_n(p, q - p);
	if (!*q)
	    continue;
	p = q + 1;
	for (q = p; *q && *q != '\t'; q++) ;
	file = Strnew_charp_n(p, q - p);
	if (!*q)
	    continue;
	p = q + 1;
	for (q = p; *q && *q != '\t'; q++) ;
	host = Strnew_charp_n(p, q - p);
	if (!*q)
	    continue;
	p = q + 1;
	for (q = p; *q && *q != '\t' && *q != '\r' && *q != '\n'; q++) ;
	port = Strnew_charp_n(p, q - p);

	switch (name->ptr[0]) {
	case '0':
	    p = "[text file]";
	    break;
	case '1':
	    p = "[directory]";
	    break;
	case 'm':
	    p = "[message]";
	    break;
	case 's':
	    p = "[sound]";
	    break;
	case 'g':
	    p = "[gif]";
	    break;
	case 'h':
	    p = "[HTML]";
	    break;
	default:
	    p = "[unsupported]";
	    break;
	}
	q = Strnew_m_charp("gopher://", host->ptr, ":", port->ptr,
			   "/", file->ptr, NULL)->ptr;
	Strcat_m_charp(tmp, "<a href=\"",
		       html_quote(url_quote_conv(q, *charset)),
		       "\">", p, html_quote(name->ptr + 1), "</a>\n", NULL);
    }

  gopher_end:
    TRAP_OFF;

    Strcat_charp(tmp, "</table>\n</body>\n</html>\n");
    return tmp;
}
#endif				/* USE_GOPHER */

/* 
 * loadBuffer: read file and make new buffer
 */
Buffer *
loadBuffer(URLFile *uf, Buffer *volatile newBuf)
{
    FILE *volatile src = NULL;
#ifdef USE_M17N
    wc_ces charset = WC_CES_US_ASCII;
    wc_ces volatile doc_charset = DocumentCharset;
#endif
    Str lineBuf2;
    volatile char pre_lbuf = '\0';
    int nlines;
    Str tmpf;
    clen_t linelen = 0, trbyte = 0;
    Lineprop *propBuffer = NULL;
#ifdef USE_ANSI_COLOR
    Linecolor *colorBuffer = NULL;
#endif
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;

    if (newBuf == NULL)
	newBuf = newBuffer(INIT_BUFFER_WIDTH);
    lineBuf2 = Strnew();

    if (SETJMP(AbortLoading) != 0) {
	goto _end;
    }
    TRAP_ON;

    if (newBuf->sourcefile == NULL &&
	(uf->scheme != SCM_LOCAL || newBuf->mailcap)) {
	tmpf = tmpfname(TMPF_SRC, NULL);
	src = fopen(tmpf->ptr, "w");
	if (src)
	    newBuf->sourcefile = tmpf->ptr;
    }
#ifdef USE_M17N
    if (newBuf->document_charset)
	charset = doc_charset = newBuf->document_charset;
    if (content_charset && UseContentCharset)
	doc_charset = content_charset;
#endif

    nlines = 0;
    if (IStype(uf->stream) != IST_ENCODED)
	uf->stream = newEncodedStream(uf->stream, uf->encoding);
    while ((lineBuf2 = StrmyISgets(uf->stream))->length) {
#ifdef USE_NNTP
	if (uf->scheme == SCM_NEWS && lineBuf2->ptr[0] == '.') {
	    Strshrinkfirst(lineBuf2, 1);
	    if (lineBuf2->ptr[0] == '\n' || lineBuf2->ptr[0] == '\r' ||
		lineBuf2->ptr[0] == '\0') {
		/*
		 * iseos(uf->stream) = TRUE;
		 */
		break;
	    }
	}
#endif				/* USE_NNTP */
	if (src)
	    Strfputs(lineBuf2, src);
	linelen += lineBuf2->length;
	if (w3m_dump & DUMP_EXTRA)
	    printf("W3m-in-progress: %s\n", convert_size2(linelen, current_content_length, TRUE));
	if (w3m_dump & DUMP_SOURCE)
	    continue;
	showProgress(&linelen, &trbyte);
	if (frame_source)
	    continue;
	lineBuf2 =
	    convertLine(uf, lineBuf2, PAGER_MODE, &charset, doc_charset);
	if (squeezeBlankLine) {
	    if (lineBuf2->ptr[0] == '\n' && pre_lbuf == '\n') {
		++nlines;
		continue;
	    }
	    pre_lbuf = lineBuf2->ptr[0];
	}
	++nlines;
	Strchop(lineBuf2);
	lineBuf2 = checkType(lineBuf2, &propBuffer, NULL);
	addnewline(newBuf, lineBuf2->ptr, propBuffer, colorBuffer,
		   lineBuf2->length, FOLD_BUFFER_WIDTH, nlines);
    }
  _end:
    TRAP_OFF;
    newBuf->topLine = newBuf->firstLine;
    newBuf->lastLine = newBuf->currentLine;
    newBuf->currentLine = newBuf->firstLine;
    newBuf->trbyte = trbyte + linelen;
#ifdef USE_M17N
    newBuf->document_charset = charset;
#endif
    if (src)
	fclose(src);

    return newBuf;
}

#ifdef USE_IMAGE
Buffer *
loadImageBuffer(URLFile *uf, Buffer *newBuf)
{
    Image image;
    ImageCache *cache;
    Str tmp, tmpf;
    FILE *src = NULL;
    URLFile f;
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
    struct stat st;

    loadImage(newBuf, IMG_FLAG_STOP);
    image.url = uf->url;
    image.ext = uf->ext;
    image.width = -1;
    image.height = -1;
    image.cache = NULL;
    cache = getImage(&image, cur_baseURL, IMG_FLAG_AUTO);
    if (!cur_baseURL->is_nocache && cache->loaded & IMG_FLAG_LOADED &&
	!stat(cache->file, &st))
	goto image_buffer;

    TRAP_ON;
    if (IStype(uf->stream) != IST_ENCODED)
	uf->stream = newEncodedStream(uf->stream, uf->encoding);
    if (save2tmp(*uf, cache->file) < 0) {
	UFclose(uf);
	TRAP_OFF;
	return NULL;
    }
    UFclose(uf);
    TRAP_OFF;

    cache->loaded = IMG_FLAG_LOADED;
    cache->index = 0;

  image_buffer:
    if (newBuf == NULL)
	newBuf = newBuffer(INIT_BUFFER_WIDTH);
    cache->loaded |= IMG_FLAG_DONT_REMOVE;
    if (newBuf->sourcefile == NULL && uf->scheme != SCM_LOCAL)
	newBuf->sourcefile = cache->file;

    tmp = Sprintf("<img src=\"%s\"><br><br>", html_quote(image.url));
    tmpf = tmpfname(TMPF_SRC, ".html");
    src = fopen(tmpf->ptr, "w");
    newBuf->mailcap_source = tmpf->ptr;

    init_stream(&f, SCM_LOCAL, newStrStream(tmp));
    loadHTMLstream(&f, newBuf, src, TRUE);
    if (src)
	fclose(src);

    newBuf->topLine = newBuf->firstLine;
    newBuf->lastLine = newBuf->currentLine;
    newBuf->currentLine = newBuf->firstLine;
    newBuf->image_flag = IMG_FLAG_AUTO;
    return newBuf;
}
#endif

static Str
conv_symbol(Line *l)
{
    Str tmp = NULL;
    char *p = l->lineBuf, *ep = p + l->len;
    Lineprop *pr = l->propBuf;
#ifdef USE_M17N
    int w;
    char **symbol = NULL;
#else
    char **symbol = get_symbol();
#endif

    for (; p < ep; p++, pr++) {
	if (*pr & PC_SYMBOL) {
#ifdef USE_M17N
	    char c = ((char)wtf_get_code((wc_uchar *) p) & 0x7f) - SYMBOL_BASE;
	    int len = get_mclen(p);
#else
	    char c = *p - SYMBOL_BASE;
#endif
	    if (tmp == NULL) {
		tmp = Strnew_size(l->len);
		Strcopy_charp_n(tmp, l->lineBuf, p - l->lineBuf);
#ifdef USE_M17N
		w = (*pr & PC_KANJI) ? 2 : 1;
		symbol = get_symbol(DisplayCharset, &w);
#endif
	    }
	    Strcat_charp(tmp, symbol[(int)c]);
#ifdef USE_M17N
	    p += len - 1;
	    pr += len - 1;
#endif
	}
	else if (tmp != NULL)
	    Strcat_char(tmp, *p);
    }
    if (tmp)
	return tmp;
    else
	return Strnew_charp_n(l->lineBuf, l->len);
}

/* 
 * saveBuffer: write buffer to file
 */
static void
_saveBuffer(Buffer *buf, Line *l, FILE * f, int cont)
{
    Str tmp;
    int is_html = FALSE;
#ifdef USE_M17N
    int set_charset = !DisplayCharset;
    wc_ces charset = DisplayCharset ? DisplayCharset : WC_CES_US_ASCII;
#endif

    is_html = is_html_type(buf->type);

  pager_next:
    for (; l != NULL; l = l->next) {
	if (is_html)
	    tmp = conv_symbol(l);
	else
	    tmp = Strnew_charp_n(l->lineBuf, l->len);
	tmp = wc_Str_conv(tmp, InnerCharset, charset);
	Strfputs(tmp, f);
	if (Strlastchar(tmp) != '\n' && !(cont && l->next && l->next->bpos))
	    putc('\n', f);
    }
    if (buf->pagerSource && !(buf->bufferprop & BP_CLOSE)) {
	l = getNextPage(buf, PagerMax);
#ifdef USE_M17N
	if (set_charset)
	    charset = buf->document_charset;
#endif
	goto pager_next;
    }
}

void
saveBuffer(Buffer *buf, FILE * f, int cont)
{
    _saveBuffer(buf, buf->firstLine, f, cont);
}

void
saveBufferBody(Buffer *buf, FILE * f, int cont)
{
    Line *l = buf->firstLine;

    while (l != NULL && l->real_linenumber == 0)
	l = l->next;
    _saveBuffer(buf, l, f, cont);
}

static Buffer *
loadcmdout(char *cmd,
	   Buffer *(*loadproc) (URLFile *, Buffer *), Buffer *defaultbuf)
{
    FILE *f, *popen(const char *, const char *);
    Buffer *buf;
    URLFile uf;

    if (cmd == NULL || *cmd == '\0')
	return NULL;
    f = popen(cmd, "r");
    if (f == NULL)
	return NULL;
    init_stream(&uf, SCM_UNKNOWN, newFileStream(f, (void (*)())pclose));
    buf = loadproc(&uf, defaultbuf);
    UFclose(&uf);
    return buf;
}

/* 
 * getshell: execute shell command and get the result into a buffer
 */
Buffer *
getshell(char *cmd)
{
    Buffer *buf;

    buf = loadcmdout(cmd, loadBuffer, NULL);
    if (buf == NULL)
	return NULL;
    buf->filename = cmd;
    buf->buffername = Sprintf("%s %s", SHELLBUFFERNAME,
			      conv_from_system(cmd))->ptr;
    return buf;
}

/* 
 * getpipe: execute shell command and connect pipe to the buffer
 */
Buffer *
getpipe(char *cmd)
{
    FILE *f, *popen(const char *, const char *);
    Buffer *buf;

    if (cmd == NULL || *cmd == '\0')
	return NULL;
    f = popen(cmd, "r");
    if (f == NULL)
	return NULL;
    buf = newBuffer(INIT_BUFFER_WIDTH);
    buf->pagerSource = newFileStream(f, (void (*)())pclose);
    buf->filename = cmd;
    buf->buffername = Sprintf("%s %s", PIPEBUFFERNAME,
			      conv_from_system(cmd))->ptr;
    buf->bufferprop |= BP_PIPE;
#ifdef USE_M17N
    buf->document_charset = WC_CES_US_ASCII;
#endif
    return buf;
}

/* 
 * Open pager buffer
 */
Buffer *
openPagerBuffer(InputStream stream, Buffer *buf)
{

    if (buf == NULL)
	buf = newBuffer(INIT_BUFFER_WIDTH);
    buf->pagerSource = stream;
    buf->buffername = getenv("MAN_PN");
    if (buf->buffername == NULL)
	buf->buffername = PIPEBUFFERNAME;
    else
	buf->buffername = conv_from_system(buf->buffername);
    buf->bufferprop |= BP_PIPE;
#ifdef USE_M17N
    if (content_charset && UseContentCharset)
	buf->document_charset = content_charset;
    else
	buf->document_charset = WC_CES_US_ASCII;
#endif
    buf->currentLine = buf->firstLine;

    return buf;
}

Buffer *
openGeneralPagerBuffer(InputStream stream)
{
    Buffer *buf;
    char *t = "text/plain";
    Buffer *t_buf = NULL;
    URLFile uf;

    init_stream(&uf, SCM_UNKNOWN, stream);

#ifdef USE_M17N
    content_charset = 0;
#endif
    if (SearchHeader) {
	t_buf = newBuffer(INIT_BUFFER_WIDTH);
	readHeader(&uf, t_buf, TRUE, NULL);
	t = checkContentType(t_buf);
	if (t == NULL)
	    t = "text/plain";
	if (t_buf) {
	    t_buf->topLine = t_buf->firstLine;
	    t_buf->currentLine = t_buf->lastLine;
	}
	SearchHeader = FALSE;
    }
    else if (DefaultType) {
	t = DefaultType;
	DefaultType = NULL;
    }
    if (is_html_type(t)) {
	buf = loadHTMLBuffer(&uf, t_buf);
	buf->type = "text/html";
    }
    else if (is_plain_text_type(t)) {
	if (IStype(stream) != IST_ENCODED)
	    stream = newEncodedStream(stream, uf.encoding);
	buf = openPagerBuffer(stream, t_buf);
	buf->type = "text/plain";
    }
#ifdef USE_IMAGE
    else if (activeImage && displayImage && !useExtImageViewer &&
	     !(w3m_dump & ~DUMP_FRAME) && !strncasecmp(t, "image/", 6)) {
	cur_baseURL = New(ParsedURL);
	parseURL("-", cur_baseURL, NULL);
	buf = loadImageBuffer(&uf, t_buf);
	buf->type = "text/html";
    }
#endif
    else {
	if (doExternal(uf, "-", t, &buf, t_buf)) {
	    UFclose(&uf);
	    if (buf == NULL || buf == NO_BUFFER)
		return buf;
	}
	else {			/* unknown type is regarded as text/plain */
	    if (IStype(stream) != IST_ENCODED)
		stream = newEncodedStream(stream, uf.encoding);
	    buf = openPagerBuffer(stream, t_buf);
	    buf->type = "text/plain";
	}
    }
    buf->real_type = t;
    buf->currentURL.scheme = SCM_LOCAL;
    buf->currentURL.file = "-";
    return buf;
}

Line *
getNextPage(Buffer *buf, int plen)
{
    Line *volatile top = buf->topLine, *volatile last = buf->lastLine,
	*volatile cur = buf->currentLine;
    int i;
    int volatile nlines = 0;
    clen_t linelen = 0, trbyte = buf->trbyte;
    Str lineBuf2;
    char volatile pre_lbuf = '\0';
    URLFile uf;
#ifdef USE_M17N
    wc_ces charset;
    wc_ces volatile doc_charset = DocumentCharset;
    wc_uint8 old_auto_detect = WcOption.auto_detect;
#endif
    int volatile squeeze_flag = FALSE;
    Lineprop *propBuffer = NULL;

#ifdef USE_ANSI_COLOR
    Linecolor *colorBuffer = NULL;
#endif
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;

    if (buf->pagerSource == NULL)
	return NULL;

    if (last != NULL) {
	nlines = last->real_linenumber;
	pre_lbuf = *(last->lineBuf);
	if (pre_lbuf == '\0')
	    pre_lbuf = '\n';
	buf->currentLine = last;
    }

#ifdef USE_M17N
    charset = buf->document_charset;
    if (buf->document_charset != WC_CES_US_ASCII)
	doc_charset = buf->document_charset;
    else if (UseContentCharset) {
	content_charset = 0;
	checkContentType(buf);
	if (content_charset)
	    doc_charset = content_charset;
    }
    WcOption.auto_detect = buf->auto_detect;
#endif

    if (SETJMP(AbortLoading) != 0) {
	goto pager_end;
    }
    TRAP_ON;

    init_stream(&uf, SCM_UNKNOWN, NULL);
    for (i = 0; i < plen; i++) {
	lineBuf2 = StrmyISgets(buf->pagerSource);
	if (lineBuf2->length == 0) {
	    /* Assume that `cmd == buf->filename' */
	    if (buf->filename)
		buf->buffername = Sprintf("%s %s",
					  CPIPEBUFFERNAME,
					  conv_from_system(buf->filename))->
		    ptr;
	    else if (getenv("MAN_PN") == NULL)
		buf->buffername = CPIPEBUFFERNAME;
	    buf->bufferprop |= BP_CLOSE;
	    break;
	}
	linelen += lineBuf2->length;
	showProgress(&linelen, &trbyte);
	lineBuf2 =
	    convertLine(&uf, lineBuf2, PAGER_MODE, &charset, doc_charset);
	if (squeezeBlankLine) {
	    squeeze_flag = FALSE;
	    if (lineBuf2->ptr[0] == '\n' && pre_lbuf == '\n') {
		++nlines;
		--i;
		squeeze_flag = TRUE;
		continue;
	    }
	    pre_lbuf = lineBuf2->ptr[0];
	}
	++nlines;
	Strchop(lineBuf2);
	lineBuf2 = checkType(lineBuf2, &propBuffer, &colorBuffer);
	addnewline(buf, lineBuf2->ptr, propBuffer, colorBuffer,
		   lineBuf2->length, FOLD_BUFFER_WIDTH, nlines);
	if (!top) {
	    top = buf->firstLine;
	    cur = top;
	}
	if (buf->lastLine->real_linenumber - buf->firstLine->real_linenumber
	    >= PagerMax) {
	    Line *l = buf->firstLine;
	    do {
		if (top == l)
		    top = l->next;
		if (cur == l)
		    cur = l->next;
		if (last == l)
		    last = NULL;
		l = l->next;
	    } while (l && l->bpos);
	    buf->firstLine = l;
	    buf->firstLine->prev = NULL;
	}
    }
  pager_end:
    TRAP_OFF;

    buf->trbyte = trbyte + linelen;
#ifdef USE_M17N
    buf->document_charset = charset;
    WcOption.auto_detect = old_auto_detect;
#endif
    buf->topLine = top;
    buf->currentLine = cur;
    if (!last)
	last = buf->firstLine;
    else if (last && (last->next || !squeeze_flag))
	last = last->next;
    return last;
}

int
save2tmp(URLFile uf, char *tmpf)
{
    FILE *ff;
    int check;
    clen_t linelen = 0, trbyte = 0;
    MySignalHandler(*volatile prevtrap) (SIGNAL_ARG) = NULL;
    static JMP_BUF env_bak;

    ff = fopen(tmpf, "wb");
    if (ff == NULL) {
	/* fclose(f); */
	return -1;
    }
    bcopy(AbortLoading, env_bak, sizeof(JMP_BUF));
    if (SETJMP(AbortLoading) != 0) {
	goto _end;
    }
    TRAP_ON;
    check = 0;
#ifdef USE_NNTP
    if (uf.scheme == SCM_NEWS) {
	char c;
	while (c = UFgetc(&uf), !iseos(uf.stream)) {
	    if (c == '\n') {
		if (check == 0)
		    check++;
		else if (check == 3)
		    break;
	    }
	    else if (c == '.' && check == 1)
		check++;
	    else if (c == '\r' && check == 2)
		check++;
	    else
		check = 0;
	    putc(c, ff);
	    linelen += sizeof(c);
	    showProgress(&linelen, &trbyte);
	}
    }
    else
#endif				/* USE_NNTP */
    {
	Str buf = Strnew_size(SAVE_BUF_SIZE);
	while (UFread(&uf, buf, SAVE_BUF_SIZE)) {
	    if (Strfputs(buf, ff) != buf->length) {
		bcopy(env_bak, AbortLoading, sizeof(JMP_BUF));
		TRAP_OFF;
		fclose(ff);
		current_content_length = 0;
		return -2;
	    }
	    linelen += buf->length;
	    showProgress(&linelen, &trbyte);
	}
    }
  _end:
    bcopy(env_bak, AbortLoading, sizeof(JMP_BUF));
    TRAP_OFF;
    fclose(ff);
    current_content_length = 0;
    return 0;
}

int
doExternal(URLFile uf, char *path, char *type, Buffer **bufp,
	   Buffer *defaultbuf)
{
    Str tmpf, command;
    struct mailcap *mcap;
    int mc_stat;
    Buffer *buf = NULL;
    char *header, *src = NULL, *ext = uf.ext;

    if (!(mcap = searchExtViewer(type)))
	return 0;

    if (mcap->nametemplate) {
	tmpf = unquote_mailcap(mcap->nametemplate, NULL, "", NULL, NULL);
	if (tmpf->ptr[0] == '.')
	    ext = tmpf->ptr;
    }
    tmpf = tmpfname(TMPF_DFL, (ext && *ext) ? ext : NULL);

    if (IStype(uf.stream) != IST_ENCODED)
	uf.stream = newEncodedStream(uf.stream, uf.encoding);
    header = checkHeader(defaultbuf, "Content-Type:");
    if (header)
	header = conv_to_system(header);
    command = unquote_mailcap(mcap->viewer, type, tmpf->ptr, header, &mc_stat);
#ifndef __EMX__
    if (!(mc_stat & MCSTAT_REPNAME)) {
	Str tmp = Sprintf("(%s) < %s", command->ptr, shell_quote(tmpf->ptr));
	command = tmp;
    }
#endif

#ifdef HAVE_SETPGRP
    if (!(mcap->flags & (MAILCAP_HTMLOUTPUT | MAILCAP_COPIOUSOUTPUT)) &&
	!(mcap->flags & MAILCAP_NEEDSTERMINAL) && BackgroundExtViewer) {
	flush_tty();
	if (!fork()) {
	    setup_child(FALSE, 0, UFfileno(&uf));
	    if (save2tmp(uf, tmpf->ptr) < 0)
		exit(1);
	    UFclose(&uf);
	    myExec(command->ptr);
	}
	*bufp = NO_BUFFER;
	return 1;
    }
    else
#endif
    {
	if (save2tmp(uf, tmpf->ptr) < 0) {
	    *bufp = NULL;
	    return 1;
	}
    }
    if (mcap->flags & (MAILCAP_HTMLOUTPUT | MAILCAP_COPIOUSOUTPUT)) {
	if (defaultbuf == NULL)
	    defaultbuf = newBuffer(INIT_BUFFER_WIDTH);
	if (defaultbuf->sourcefile)
	    src = defaultbuf->sourcefile;
	else
	    src = tmpf->ptr;
	defaultbuf->sourcefile = NULL;
	defaultbuf->mailcap = mcap;
    }
    if (mcap->flags & MAILCAP_HTMLOUTPUT) {
	buf = loadcmdout(command->ptr, loadHTMLBuffer, defaultbuf);
	if (buf && buf != NO_BUFFER) {
	    buf->type = "text/html";
	    buf->mailcap_source = buf->sourcefile;
	    buf->sourcefile = src;
	}
    }
    else if (mcap->flags & MAILCAP_COPIOUSOUTPUT) {
	buf = loadcmdout(command->ptr, loadBuffer, defaultbuf);
	if (buf && buf != NO_BUFFER) {
	    buf->type = "text/plain";
	    buf->mailcap_source = buf->sourcefile;
	    buf->sourcefile = src;
	}
    }
    else {
	if (mcap->flags & MAILCAP_NEEDSTERMINAL || !BackgroundExtViewer) {
	    fmTerm();
	    mySystem(command->ptr, 0);
	    fmInit();
	    if (CurrentTab && Currentbuf)
		displayBuffer(Currentbuf, B_FORCE_REDRAW);
	}
	else {
	    mySystem(command->ptr, 1);
	}
	buf = NO_BUFFER;
    }
    if (buf && buf != NO_BUFFER) {
	buf->filename = path;
	if (buf->buffername == NULL || buf->buffername[0] == '\0')
	    buf->buffername = conv_from_system(lastFileName(path));
	buf->edit = mcap->edit;
	buf->mailcap = mcap;
    }
    *bufp = buf;
    return 1;
}

static int
_MoveFile(char *path1, char *path2)
{
    InputStream f1;
    FILE *f2;
    int is_pipe;
    clen_t linelen = 0, trbyte = 0;
    Str buf;

    f1 = openIS(path1);
    if (f1 == NULL)
	return -1;
    if (*path2 == '|' && PermitSaveToPipe) {
	is_pipe = TRUE;
	f2 = popen(path2 + 1, "w");
    }
    else {
	is_pipe = FALSE;
	f2 = fopen(path2, "wb");
    }
    if (f2 == NULL) {
	ISclose(f1);
	return -1;
    }
    current_content_length = 0;
    buf = Strnew_size(SAVE_BUF_SIZE);
    while (ISread(f1, buf, SAVE_BUF_SIZE)) {
	Strfputs(buf, f2);
	linelen += buf->length;
	showProgress(&linelen, &trbyte);
    }
    ISclose(f1);
    if (is_pipe)
	pclose(f2);
    else
	fclose(f2);
    return 0;
}

int
_doFileCopy(char *tmpf, char *defstr, int download)
{
#ifndef __MINGW32_VERSION
    Str msg;
    Str filen;
    char *p, *q = NULL;
    pid_t pid;
    char *lock;
#if !(defined(HAVE_SYMLINK) && defined(HAVE_LSTAT))
    FILE *f;
#endif
    struct stat st;
    clen_t size = 0;
    int is_pipe = FALSE;

    if (fmInitialized) {
	p = searchKeyData();
	if (p == NULL || *p == '\0') {
	    /* FIXME: gettextize? */
	    q = inputLineHist("(Download)Save file to: ",
			      defstr, IN_COMMAND, SaveHist);
	    if (q == NULL || *q == '\0')
		return FALSE;
	    p = conv_to_system(q);
	}
	if (*p == '|' && PermitSaveToPipe)
	    is_pipe = TRUE;
	else {
	    if (q) {
		p = unescape_spaces(Strnew_charp(q))->ptr;
		p = conv_to_system(q);
	    }
	    p = expandPath(p);
	    if (checkOverWrite(p) < 0)
		return -1;
	}
	if (checkCopyFile(tmpf, p) < 0) {
	    /* FIXME: gettextize? */
	    msg = Sprintf("Can't copy. %s and %s are identical.",
			  conv_from_system(tmpf), conv_from_system(p));
	    disp_err_message(msg->ptr, FALSE);
	    return -1;
	}
	if (!download) {
	    if (_MoveFile(tmpf, p) < 0) {
		/* FIXME: gettextize? */
		msg = Sprintf("Can't save to %s", conv_from_system(p));
		disp_err_message(msg->ptr, FALSE);
	    }
	    return -1;
	}
	lock = tmpfname(TMPF_DFL, ".lock")->ptr;
#if defined(HAVE_SYMLINK) && defined(HAVE_LSTAT)
	symlink(p, lock);
#else
	f = fopen(lock, "w");
	if (f)
	    fclose(f);
#endif
	flush_tty();
	pid = fork();
	if (!pid) {
	    setup_child(FALSE, 0, -1);
	    if (!_MoveFile(tmpf, p) && PreserveTimestamp && !is_pipe &&
		!stat(tmpf, &st))
		setModtime(p, st.st_mtime);
	    unlink(lock);
	    exit(0);
	}
	if (!stat(tmpf, &st))
	    size = st.st_size;
	addDownloadList(pid, conv_from_system(tmpf), p, lock, size);
    }
    else {
	q = searchKeyData();
	if (q == NULL || *q == '\0') {
	    /* FIXME: gettextize? */
	    printf("(Download)Save file to: ");
	    fflush(stdout);
	    filen = Strfgets(stdin);
	    if (filen->length == 0)
		return -1;
	    q = filen->ptr;
	}
	for (p = q + strlen(q) - 1; IS_SPACE(*p); p--) ;
	*(p + 1) = '\0';
	if (*q == '\0')
	    return -1;
	p = q;
	if (*p == '|' && PermitSaveToPipe)
	    is_pipe = TRUE;
	else {
	    p = expandPath(p);
	    if (checkOverWrite(p) < 0)
		return -1;
	}
	if (checkCopyFile(tmpf, p) < 0) {
	    /* FIXME: gettextize? */
	    printf("Can't copy. %s and %s are identical.", tmpf, p);
	    return -1;
	}
	if (_MoveFile(tmpf, p) < 0) {
	    /* FIXME: gettextize? */
	    printf("Can't save to %s\n", p);
	    return -1;
	}
	if (PreserveTimestamp && !is_pipe && !stat(tmpf, &st))
	    setModtime(p, st.st_mtime);
    }
#endif /* __MINGW32_VERSION */
    return 0;
}

int
doFileMove(char *tmpf, char *defstr)
{
    int ret = doFileCopy(tmpf, defstr);
    unlink(tmpf);
    return ret;
}

int
doFileSave(URLFile uf, char *defstr)
{
#ifndef __MINGW32_VERSION
    Str msg;
    Str filen;
    char *p, *q;
    pid_t pid;
    char *lock;
    char *tmpf = NULL; 
#if !(defined(HAVE_SYMLINK) && defined(HAVE_LSTAT))
    FILE *f;
#endif

    if (fmInitialized) {
	p = searchKeyData();
	if (p == NULL || *p == '\0') {
	    /* FIXME: gettextize? */
	    p = inputLineHist("(Download)Save file to: ",
			      defstr, IN_FILENAME, SaveHist);
	    if (p == NULL || *p == '\0')
		return -1;
	    p = conv_to_system(p);
	}
	if (checkOverWrite(p) < 0)
	    return -1;
	if (checkSaveFile(uf.stream, p) < 0) {
	    /* FIXME: gettextize? */
	    msg = Sprintf("Can't save. Load file and %s are identical.",
			  conv_from_system(p));
	    disp_err_message(msg->ptr, FALSE);
	    return -1;
	}
	/*
	 * if (save2tmp(uf, p) < 0) {
	 * msg = Sprintf("Can't save to %s", conv_from_system(p));
	 * disp_err_message(msg->ptr, FALSE);
	 * }
	 */
	lock = tmpfname(TMPF_DFL, ".lock")->ptr;
#if defined(HAVE_SYMLINK) && defined(HAVE_LSTAT)
	symlink(p, lock);
#else
	f = fopen(lock, "w");
	if (f)
	    fclose(f);
#endif
	flush_tty();
	pid = fork();
	if (!pid) {
	    int err;
	    if ((uf.content_encoding != CMP_NOCOMPRESS) && AutoUncompress) {
		uncompress_stream(&uf, &tmpf);
		if (tmpf)
		    unlink(tmpf);
	    }
	    setup_child(FALSE, 0, UFfileno(&uf));
	    err = save2tmp(uf, p);
	    if (err == 0 && PreserveTimestamp && uf.modtime != -1)
		setModtime(p, uf.modtime);
	    UFclose(&uf);
	    unlink(lock);
	    if (err != 0)
		exit(-err);
	    exit(0);
	}
	addDownloadList(pid, uf.url, p, lock, current_content_length);
    }
    else {
	q = searchKeyData();
	if (q == NULL || *q == '\0') {
	    /* FIXME: gettextize? */
	    printf("(Download)Save file to: ");
	    fflush(stdout);
	    filen = Strfgets(stdin);
	    if (filen->length == 0)
		return -1;
	    q = filen->ptr;
	}
	for (p = q + strlen(q) - 1; IS_SPACE(*p); p--) ;
	*(p + 1) = '\0';
	if (*q == '\0')
	    return -1;
	p = expandPath(q);
	if (checkOverWrite(p) < 0)
	    return -1;
	if (checkSaveFile(uf.stream, p) < 0) {
	    /* FIXME: gettextize? */
	    printf("Can't save. Load file and %s are identical.", p);
	    return -1;
	}
	if (uf.content_encoding != CMP_NOCOMPRESS && AutoUncompress) {
	    uncompress_stream(&uf, &tmpf);
	    if (tmpf)
		unlink(tmpf);
	}
	if (save2tmp(uf, p) < 0) {
	    /* FIXME: gettextize? */
	    printf("Can't save to %s\n", p);
	    return -1;
	}
	if (PreserveTimestamp && uf.modtime != -1)
	    setModtime(p, uf.modtime);
    }
#endif /* __MINGW32_VERSION */
    return 0;
}

int
checkCopyFile(char *path1, char *path2)
{
    struct stat st1, st2;

    if (*path2 == '|' && PermitSaveToPipe)
	return 0;
    if ((stat(path1, &st1) == 0) && (stat(path2, &st2) == 0))
	if (st1.st_ino == st2.st_ino)
	    return -1;
    return 0;
}

int
checkSaveFile(InputStream stream, char *path2)
{
    struct stat st1, st2;
    int des = ISfileno(stream);

    if (des < 0)
	return 0;
    if (*path2 == '|' && PermitSaveToPipe)
	return 0;
    if ((fstat(des, &st1) == 0) && (stat(path2, &st2) == 0))
	if (st1.st_ino == st2.st_ino)
	    return -1;
    return 0;
}

int
checkOverWrite(char *path)
{
    struct stat st;
    char *ans;

    if (stat(path, &st) < 0)
	return 0;
    /* FIXME: gettextize? */
    ans = inputAnswer("File exists. Overwrite? (y/n)");
    if (ans && TOLOWER(*ans) == 'y')
	return 0;
    else
	return -1;
}

char *
inputAnswer(char *prompt)
{
    char *ans;

    if (QuietMessage)
	return "n";
    if (fmInitialized) {
	term_raw();
	ans = inputChar(prompt);
    }
    else {
	printf("%s", prompt);
	fflush(stdout);
	ans = Strfgets(stdin)->ptr;
    }
    return ans;
}

static void
uncompress_stream(URLFile *uf, char **src)
{
#ifndef __MINGW32_VERSION
    pid_t pid1;
    FILE *f1;
    char *expand_cmd = GUNZIP_CMDNAME;
    char *expand_name = GUNZIP_NAME;
    char *tmpf = NULL;
    char *ext = NULL;
    struct compression_decoder *d;

    if (IStype(uf->stream) != IST_ENCODED) {
	uf->stream = newEncodedStream(uf->stream, uf->encoding);
	uf->encoding = ENC_7BIT;
    }
    for (d = compression_decoders; d->type != CMP_NOCOMPRESS; d++) {
	if (uf->compression == d->type) {
	    if (d->auxbin_p)
		expand_cmd = auxbinFile(d->cmd);
	    else
		expand_cmd = d->cmd;
	    expand_name = d->name;
	    ext = d->ext;
	    break;
	}
    }
    uf->compression = CMP_NOCOMPRESS;

    if (uf->scheme != SCM_LOCAL
#ifdef USE_IMAGE
	&& !image_source
#endif
	) {
	tmpf = tmpfname(TMPF_DFL, ext)->ptr;
    }

    /* child1 -- stdout|f1=uf -> parent */
    pid1 = open_pipe_rw(&f1, NULL);
    if (pid1 < 0) {
	UFclose(uf);
	return;
    }
    if (pid1 == 0) {
	/* child */
	pid_t pid2;
	FILE *f2 = stdin;

	/* uf -> child2 -- stdout|stdin -> child1 */
	pid2 = open_pipe_rw(&f2, NULL);
	if (pid2 < 0) {
	    UFclose(uf);
	    exit(1);
	}
	if (pid2 == 0) {
	    /* child2 */
	    Str buf = Strnew_size(SAVE_BUF_SIZE);
	    FILE *f = NULL;

	    setup_child(TRUE, 2, UFfileno(uf));
	    if (tmpf)
		f = fopen(tmpf, "wb");
	    while (UFread(uf, buf, SAVE_BUF_SIZE)) {
		if (Strfputs(buf, stdout) < 0)
		    break;
		if (f)
		    Strfputs(buf, f);
	    }
	    UFclose(uf);
	    if (f)
		fclose(f);
	    exit(0);
	}
	/* child1 */
	dup2(1, 2);		/* stderr>&stdout */
	setup_child(TRUE, -1, -1);
	execlp(expand_cmd, expand_name, NULL);
	exit(1);
    }
    if (tmpf) {
	if (src)
	    *src = tmpf;
	else
	    uf->scheme = SCM_LOCAL;
    }
    UFhalfclose(uf);
    uf->stream = newFileStream(f1, (void (*)())fclose);
#endif /* __MINGW32_VERSION */
}

static FILE *
lessopen_stream(char *path)
{
    char *lessopen;
    FILE *fp;

    lessopen = getenv("LESSOPEN");
    if (lessopen == NULL) {
	return NULL;
    }
    if (lessopen[0] == '\0') {
	return NULL;
    }

    if (lessopen[0] == '|') {
	/* pipe mode */
	Str tmpf;
	int c;

	++lessopen;
	tmpf = Sprintf(lessopen, shell_quote(path));
	fp = popen(tmpf->ptr, "r");
	if (fp == NULL) {
	    return NULL;
	}
	c = getc(fp);
	if (c == EOF) {
	    fclose(fp);
	    return NULL;
	}
	ungetc(c, fp);
    }
    else {
	/* filename mode */
	/* not supported m(__)m */
	fp = NULL;
    }
    return fp;
}

#if 0
void
reloadBuffer(Buffer *buf)
{
    URLFile uf;

    if (buf->sourcefile == NULL || buf->pagerSource != NULL)
	return;
    init_stream(&uf, SCM_UNKNOWN, NULL);
    examineFile(buf->mailcap_source ? buf->mailcap_source : buf->sourcefile,
		&uf);
    if (uf.stream == NULL)
	return;
    is_redisplay = TRUE;
    buf->allLine = 0;
    buf->href = NULL;
    buf->name = NULL;
    buf->img = NULL;
    buf->formitem = NULL;
    buf->linklist = NULL;
    buf->maplist = NULL;
    if (buf->hmarklist)
	buf->hmarklist->nmark = 0;
    if (buf->imarklist)
	buf->imarklist->nmark = 0;
    if (is_html_type(buf->type))
	loadHTMLBuffer(&uf, buf);
    else
	loadBuffer(&uf, buf);
    UFclose(&uf);
    is_redisplay = FALSE;
}
#endif

static char *
guess_filename(char *file)
{
    char *p = NULL, *s;

    if (file != NULL)
	p = mybasename(file);
    if (p == NULL || *p == '\0')
	return DEF_SAVE_FILE;
    s = p;
    if (*p == '#')
	p++;
    while (*p != '\0') {
	if ((*p == '#' && *(p + 1) != '\0') || *p == '?') {
	    *p = '\0';
	    break;
	}
	p++;
    }
    return s;
}

char *
guess_save_name(Buffer *buf, char *path)
{
    if (buf && buf->document_header) {
	Str name = NULL;
	char *p, *q;
	if ((p = checkHeader(buf, "Content-Disposition:")) != NULL &&
	    (q = strcasestr(p, "filename")) != NULL &&
	    (q == p || IS_SPACE(*(q - 1)) || *(q - 1) == ';') &&
	    matchattr(q, "filename", 8, &name))
	    path = name->ptr;
	else if ((p = checkHeader(buf, "Content-Type:")) != NULL &&
		 (q = strcasestr(p, "name")) != NULL &&
		 (q == p || IS_SPACE(*(q - 1)) || *(q - 1) == ';') &&
		 matchattr(q, "name", 4, &name))
	    path = name->ptr;
    }
    return guess_filename(path);
}

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */